// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <stddef.h>
#include <stdint.h>

#include <limits>
#include <memory>
#include <set>
#include <string>
#include <utility>
#include <vector>

#include "base/bind.h"
#include "base/files/file.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/macros.h"
#include "base/run_loop.h"
#include "base/threading/thread_task_runner_handle.h"
#include "build/build_config.h"
#include "content/browser/fileapi/mock_file_change_observer.h"
#include "content/public/test/async_file_test_helper.h"
#include "content/public/test/mock_special_storage_policy.h"
#include "content/public/test/sandbox_file_system_test_helper.h"
#include "content/public/test/test_file_system_context.h"
#include "content/test/fileapi_test_file_set.h"
#include "storage/browser/fileapi/external_mount_points.h"
#include "storage/browser/fileapi/file_system_backend.h"
#include "storage/browser/fileapi/file_system_context.h"
#include "storage/browser/fileapi/file_system_operation_context.h"
#include "storage/browser/fileapi/file_system_usage_cache.h"
#include "storage/browser/fileapi/obfuscated_file_util.h"
#include "storage/browser/fileapi/sandbox_directory_database.h"
#include "storage/browser/fileapi/sandbox_file_system_backend_delegate.h"
#include "storage/browser/fileapi/sandbox_isolated_origin_database.h"
#include "storage/browser/fileapi/sandbox_origin_database.h"
#include "storage/browser/quota/quota_manager.h"
#include "storage/common/database/database_identifier.h"
#include "storage/common/quota/quota_types.h"
#include "testing/gtest/include/gtest/gtest.h"

using content::AsyncFileTestHelper;
using storage::FileSystemContext;
using storage::FileSystemOperation;
using storage::FileSystemOperationContext;
using storage::FileSystemURL;
using storage::kFileSystemTypePersistent;
using storage::kFileSystemTypeTemporary;
using storage::ObfuscatedFileUtil;
using storage::SandboxDirectoryDatabase;
using storage::SandboxIsolatedOriginDatabase;

namespace content {

namespace {

    bool FileExists(const base::FilePath& path)
    {
        return base::PathExists(path) && !base::DirectoryExists(path);
    }

    int64_t GetSize(const base::FilePath& path)
    {
        int64_t size;
        EXPECT_TRUE(base::GetFileSize(path, &size));
        return size;
    }

    // After a move, the dest exists and the source doesn't.
    // After a copy, both source and dest exist.
    struct CopyMoveTestCaseRecord {
        bool is_copy_not_move;
        const char source_path[64];
        const char dest_path[64];
        bool cause_overwrite;
    };

    const CopyMoveTestCaseRecord kCopyMoveTestCases[] = {
        // This is the combinatoric set of:
        //  rename vs. same-name
        //  different directory vs. same directory
        //  overwrite vs. no-overwrite
        //  copy vs. move
        //  We can never be called with source and destination paths identical, so
        //  those cases are omitted.
        { true, "dir0/file0", "dir0/file1", false },
        { false, "dir0/file0", "dir0/file1", false },
        { true, "dir0/file0", "dir0/file1", true },
        { false, "dir0/file0", "dir0/file1", true },

        { true, "dir0/file0", "dir1/file0", false },
        { false, "dir0/file0", "dir1/file0", false },
        { true, "dir0/file0", "dir1/file0", true },
        { false, "dir0/file0", "dir1/file0", true },
        { true, "dir0/file0", "dir1/file1", false },
        { false, "dir0/file0", "dir1/file1", false },
        { true, "dir0/file0", "dir1/file1", true },
        { false, "dir0/file0", "dir1/file1", true },
    };

    struct OriginEnumerationTestRecord {
        std::string origin_url;
        bool has_temporary;
        bool has_persistent;
    };

    const OriginEnumerationTestRecord kOriginEnumerationTestRecords[] = {
        { "http://example.com/", false, true },
        { "http://example1.com/", true, false },
        { "https://example1.com/", true, true },
        { "file:///", false, true },
        { "http://example.com:8000/", false, true },
    };

    FileSystemURL FileSystemURLAppend(
        const FileSystemURL& url, const base::FilePath::StringType& child)
    {
        return FileSystemURL::CreateForTest(
            url.origin(), url.mount_type(), url.virtual_path().Append(child));
    }

    FileSystemURL FileSystemURLAppendUTF8(
        const FileSystemURL& url, const std::string& child)
    {
        return FileSystemURL::CreateForTest(
            url.origin(),
            url.mount_type(),
            url.virtual_path().Append(base::FilePath::FromUTF8Unsafe(child)));
    }

    FileSystemURL FileSystemURLDirName(const FileSystemURL& url)
    {
        return FileSystemURL::CreateForTest(
            url.origin(),
            url.mount_type(),
            storage::VirtualPath::DirName(url.virtual_path()));
    }

    std::string GetTypeString(storage::FileSystemType type)
    {
        return storage::SandboxFileSystemBackendDelegate::GetTypeString(type);
    }

    bool HasFileSystemType(ObfuscatedFileUtil::AbstractOriginEnumerator* enumerator,
        storage::FileSystemType type)
    {
        return enumerator->HasTypeDirectory(GetTypeString(type));
    }

} // namespace

// TODO(ericu): The vast majority of this and the other FSFU subclass tests
// could theoretically be shared.  It would basically be a FSFU interface
// compliance test, and only the subclass-specific bits that look into the
// implementation would need to be written per-subclass.
class ObfuscatedFileUtilTest : public testing::Test {
public:
    ObfuscatedFileUtilTest()
        : origin_(GURL("http://www.example.com"))
        , type_(storage::kFileSystemTypeTemporary)
        , sandbox_file_system_(origin_, type_)
        , quota_status_(storage::kQuotaStatusUnknown)
        , usage_(-1)
        , weak_factory_(this)
    {
    }

    void SetUp() override
    {
        ASSERT_TRUE(data_dir_.CreateUniqueTempDir());

        storage_policy_ = new MockSpecialStoragePolicy();

        quota_manager_ = new storage::QuotaManager(
            false /* is_incognito */, data_dir_.GetPath(),
            base::ThreadTaskRunnerHandle::Get().get(),
            base::ThreadTaskRunnerHandle::Get().get(), storage_policy_.get());

        // Every time we create a new sandbox_file_system helper,
        // it creates another context, which creates another path manager,
        // another sandbox_backend, and another OFU.
        // We need to pass in the context to skip all that.
        file_system_context_ = CreateFileSystemContextForTesting(
            quota_manager_->proxy(), data_dir_.GetPath());

        sandbox_file_system_.SetUp(file_system_context_.get());

        change_observers_ = storage::MockFileChangeObserver::CreateList(&change_observer_);
    }

    void TearDown() override
    {
        quota_manager_ = NULL;
        sandbox_file_system_.TearDown();
    }

    std::unique_ptr<FileSystemOperationContext> LimitedContext(
        int64_t allowed_bytes_growth)
    {
        std::unique_ptr<FileSystemOperationContext> context(
            sandbox_file_system_.NewOperationContext());
        context->set_allowed_bytes_growth(allowed_bytes_growth);
        return context;
    }

    std::unique_ptr<FileSystemOperationContext> UnlimitedContext()
    {
        return LimitedContext(std::numeric_limits<int64_t>::max());
    }

    FileSystemOperationContext* NewContext(
        SandboxFileSystemTestHelper* file_system)
    {
        change_observer()->ResetCount();
        FileSystemOperationContext* context;
        if (file_system)
            context = file_system->NewOperationContext();
        else
            context = sandbox_file_system_.NewOperationContext();
        // Setting allowed_bytes_growth big enough for all tests.
        context->set_allowed_bytes_growth(1024 * 1024);
        context->set_change_observers(change_observers());
        return context;
    }

    const storage::ChangeObserverList& change_observers() const
    {
        return change_observers_;
    }

    storage::MockFileChangeObserver* change_observer()
    {
        return &change_observer_;
    }

    // This can only be used after SetUp has run and created file_system_context_
    // and obfuscated_file_util_.
    // Use this for tests which need to run in multiple origins; we need a test
    // helper per origin.
    SandboxFileSystemTestHelper* NewFileSystem(const GURL& origin,
        storage::FileSystemType type)
    {
        SandboxFileSystemTestHelper* file_system = new SandboxFileSystemTestHelper(origin, type);

        file_system->SetUp(file_system_context_.get());
        return file_system;
    }

    std::unique_ptr<ObfuscatedFileUtil> CreateObfuscatedFileUtil(
        storage::SpecialStoragePolicy* storage_policy)
    {
        return std::unique_ptr<ObfuscatedFileUtil>(
            ObfuscatedFileUtil::CreateForTesting(
                storage_policy, data_dir_path(), NULL,
                base::ThreadTaskRunnerHandle::Get().get()));
    }

    ObfuscatedFileUtil* ofu()
    {
        return static_cast<ObfuscatedFileUtil*>(sandbox_file_system_.file_util());
    }

    const base::FilePath& test_directory() const { return data_dir_.GetPath(); }

    const GURL& origin() const
    {
        return origin_;
    }

    storage::FileSystemType type() const { return type_; }

    std::string type_string() const
    {
        return GetTypeString(type_);
    }

    int64_t ComputeTotalFileSize()
    {
        return sandbox_file_system_.ComputeCurrentOriginUsage() - sandbox_file_system_.ComputeCurrentDirectoryDatabaseUsage();
    }

    void GetUsageFromQuotaManager()
    {
        int64_t quota = -1;
        quota_status_ = AsyncFileTestHelper::GetUsageAndQuota(quota_manager_.get(),
            origin(),
            sandbox_file_system_.type(),
            &usage_,
            &quota);
        EXPECT_EQ(storage::kQuotaStatusOk, quota_status_);
    }

    void RevokeUsageCache()
    {
        quota_manager_->ResetUsageTracker(sandbox_file_system_.storage_type());
        usage_cache()->Delete(sandbox_file_system_.GetUsageCachePath());
    }

    int64_t SizeByQuotaUtil()
    {
        return sandbox_file_system_.GetCachedOriginUsage();
    }

    int64_t SizeInUsageFile()
    {
        base::RunLoop().RunUntilIdle();
        int64_t usage = 0;
        return usage_cache()->GetUsage(
                   sandbox_file_system_.GetUsageCachePath(), &usage)
            ? usage
            : -1;
    }

    bool PathExists(const FileSystemURL& url)
    {
        std::unique_ptr<FileSystemOperationContext> context(NewContext(NULL));
        base::File::Info file_info;
        base::FilePath platform_path;
        base::File::Error error = ofu()->GetFileInfo(
            context.get(), url, &file_info, &platform_path);
        return error == base::File::FILE_OK;
    }

    bool DirectoryExists(const FileSystemURL& url)
    {
        return AsyncFileTestHelper::DirectoryExists(file_system_context(), url);
    }

    int64_t usage() const { return usage_; }
    storage::FileSystemUsageCache* usage_cache()
    {
        return sandbox_file_system_.usage_cache();
    }

    FileSystemURL CreateURLFromUTF8(const std::string& path)
    {
        return sandbox_file_system_.CreateURLFromUTF8(path);
    }

    int64_t PathCost(const FileSystemURL& url)
    {
        return ObfuscatedFileUtil::ComputeFilePathCost(url.path());
    }

    FileSystemURL CreateURL(const base::FilePath& path)
    {
        return sandbox_file_system_.CreateURL(path);
    }

    void CheckFileAndCloseHandle(const FileSystemURL& url, base::File file)
    {
        std::unique_ptr<FileSystemOperationContext> context(NewContext(NULL));
        base::FilePath local_path;
        EXPECT_EQ(base::File::FILE_OK,
            ofu()->GetLocalFilePath(context.get(), url, &local_path));

        base::File::Info file_info0;
        base::FilePath data_path;
        EXPECT_EQ(base::File::FILE_OK,
            ofu()->GetFileInfo(context.get(), url, &file_info0, &data_path));
        EXPECT_EQ(data_path, local_path);
        EXPECT_TRUE(FileExists(data_path));
        EXPECT_EQ(0, GetSize(data_path));

        const char data[] = "test data";
        const int length = arraysize(data) - 1;

        if (!file.IsValid()) {
            file.Initialize(data_path,
                base::File::FLAG_OPEN | base::File::FLAG_WRITE);
            ASSERT_TRUE(file.IsValid());
            EXPECT_FALSE(file.created());
        }
        ASSERT_EQ(length, file.Write(0, data, length));
        file.Close();

        base::File::Info file_info1;
        EXPECT_EQ(length, GetSize(data_path));
        context.reset(NewContext(NULL));
        EXPECT_EQ(base::File::FILE_OK,
            ofu()->GetFileInfo(context.get(), url, &file_info1, &data_path));
        EXPECT_EQ(data_path, local_path);

        EXPECT_FALSE(file_info0.is_directory);
        EXPECT_FALSE(file_info1.is_directory);
        EXPECT_FALSE(file_info0.is_symbolic_link);
        EXPECT_FALSE(file_info1.is_symbolic_link);
        EXPECT_EQ(0, file_info0.size);
        EXPECT_EQ(length, file_info1.size);
        EXPECT_LE(file_info0.last_modified, file_info1.last_modified);

        context.reset(NewContext(NULL));
        EXPECT_EQ(base::File::FILE_OK,
            ofu()->Truncate(context.get(), url, length * 2));
        EXPECT_EQ(length * 2, GetSize(data_path));

        context.reset(NewContext(NULL));
        EXPECT_EQ(base::File::FILE_OK,
            ofu()->Truncate(context.get(), url, 0));
        EXPECT_EQ(0, GetSize(data_path));
    }

    void ValidateTestDirectory(
        const FileSystemURL& root_url,
        const std::set<base::FilePath::StringType>& files,
        const std::set<base::FilePath::StringType>& directories)
    {
        std::unique_ptr<FileSystemOperationContext> context;
        std::set<base::FilePath::StringType>::const_iterator iter;
        for (iter = files.begin(); iter != files.end(); ++iter) {
            bool created = true;
            context.reset(NewContext(NULL));
            ASSERT_EQ(base::File::FILE_OK,
                ofu()->EnsureFileExists(context.get(),
                    FileSystemURLAppend(root_url, *iter),
                    &created));
            ASSERT_FALSE(created);
        }
        for (iter = directories.begin(); iter != directories.end(); ++iter) {
            context.reset(NewContext(NULL));
            EXPECT_TRUE(DirectoryExists(
                FileSystemURLAppend(root_url, *iter)));
        }
    }

    class UsageVerifyHelper {
    public:
        UsageVerifyHelper(std::unique_ptr<FileSystemOperationContext> context,
            SandboxFileSystemTestHelper* file_system,
            int64_t expected_usage)
            : context_(std::move(context))
            , sandbox_file_system_(file_system)
            , expected_usage_(expected_usage)
        {
        }

        ~UsageVerifyHelper()
        {
            base::RunLoop().RunUntilIdle();
            Check();
        }

        FileSystemOperationContext* context()
        {
            return context_.get();
        }

    private:
        void Check()
        {
            ASSERT_EQ(expected_usage_,
                sandbox_file_system_->GetCachedOriginUsage());
        }

        std::unique_ptr<FileSystemOperationContext> context_;
        SandboxFileSystemTestHelper* sandbox_file_system_;
        int64_t expected_usage_;
    };

    std::unique_ptr<UsageVerifyHelper> AllowUsageIncrease(
        int64_t requested_growth)
    {
        int64_t usage = sandbox_file_system_.GetCachedOriginUsage();
        return std::unique_ptr<UsageVerifyHelper>(
            new UsageVerifyHelper(LimitedContext(requested_growth),
                &sandbox_file_system_, usage + requested_growth));
    }

    std::unique_ptr<UsageVerifyHelper> DisallowUsageIncrease(
        int64_t requested_growth)
    {
        int64_t usage = sandbox_file_system_.GetCachedOriginUsage();
        return std::unique_ptr<UsageVerifyHelper>(new UsageVerifyHelper(
            LimitedContext(requested_growth - 1), &sandbox_file_system_, usage));
    }

    void FillTestDirectory(
        const FileSystemURL& root_url,
        std::set<base::FilePath::StringType>* files,
        std::set<base::FilePath::StringType>* directories)
    {
        std::unique_ptr<FileSystemOperationContext> context;
        std::vector<storage::DirectoryEntry> entries;
        EXPECT_EQ(base::File::FILE_OK,
            AsyncFileTestHelper::ReadDirectory(file_system_context(),
                root_url, &entries));
        EXPECT_EQ(0UL, entries.size());

        files->clear();
        files->insert(FILE_PATH_LITERAL("first"));
        files->insert(FILE_PATH_LITERAL("second"));
        files->insert(FILE_PATH_LITERAL("third"));
        directories->clear();
        directories->insert(FILE_PATH_LITERAL("fourth"));
        directories->insert(FILE_PATH_LITERAL("fifth"));
        directories->insert(FILE_PATH_LITERAL("sixth"));
        std::set<base::FilePath::StringType>::iterator iter;
        for (iter = files->begin(); iter != files->end(); ++iter) {
            bool created = false;
            context.reset(NewContext(NULL));
            ASSERT_EQ(base::File::FILE_OK,
                ofu()->EnsureFileExists(context.get(),
                    FileSystemURLAppend(root_url, *iter),
                    &created));
            ASSERT_TRUE(created);
        }
        for (iter = directories->begin(); iter != directories->end(); ++iter) {
            bool exclusive = true;
            bool recursive = false;
            context.reset(NewContext(NULL));
            EXPECT_EQ(base::File::FILE_OK,
                ofu()->CreateDirectory(context.get(),
                    FileSystemURLAppend(root_url, *iter),
                    exclusive, recursive));
        }
        ValidateTestDirectory(root_url, *files, *directories);
    }

    void TestReadDirectoryHelper(const FileSystemURL& root_url)
    {
        std::set<base::FilePath::StringType> files;
        std::set<base::FilePath::StringType> directories;
        FillTestDirectory(root_url, &files, &directories);

        std::unique_ptr<FileSystemOperationContext> context;
        std::vector<storage::DirectoryEntry> entries;
        context.reset(NewContext(NULL));
        EXPECT_EQ(base::File::FILE_OK,
            AsyncFileTestHelper::ReadDirectory(
                file_system_context(), root_url, &entries));
        std::vector<storage::DirectoryEntry>::iterator entry_iter;
        EXPECT_EQ(files.size() + directories.size(), entries.size());
        EXPECT_TRUE(change_observer()->HasNoChange());
        for (entry_iter = entries.begin(); entry_iter != entries.end();
             ++entry_iter) {
            const storage::DirectoryEntry& entry = *entry_iter;
            std::set<base::FilePath::StringType>::iterator iter = files.find(entry.name);
            if (iter != files.end()) {
                EXPECT_FALSE(entry.is_directory);
                files.erase(iter);
                continue;
            }
            iter = directories.find(entry.name);
            EXPECT_FALSE(directories.end() == iter);
            EXPECT_TRUE(entry.is_directory);
            directories.erase(iter);
        }
    }

    void TestTouchHelper(const FileSystemURL& url, bool is_file)
    {
        base::Time last_access_time = base::Time::Now();
        base::Time last_modified_time = base::Time::Now();

        std::unique_ptr<FileSystemOperationContext> context(NewContext(NULL));
        EXPECT_EQ(base::File::FILE_OK,
            ofu()->Touch(context.get(), url, last_access_time,
                last_modified_time));
        // Currently we fire no change notifications for Touch.
        EXPECT_TRUE(change_observer()->HasNoChange());
        base::FilePath local_path;
        base::File::Info file_info;
        context.reset(NewContext(NULL));
        EXPECT_EQ(base::File::FILE_OK, ofu()->GetFileInfo(context.get(), url, &file_info, &local_path));
        // We compare as time_t here to lower our resolution, to avoid false
        // negatives caused by conversion to the local filesystem's native
        // representation and back.
        EXPECT_EQ(file_info.last_modified.ToTimeT(), last_modified_time.ToTimeT());

        context.reset(NewContext(NULL));
        last_modified_time += base::TimeDelta::FromHours(1);
        last_access_time += base::TimeDelta::FromHours(14);
        EXPECT_EQ(base::File::FILE_OK,
            ofu()->Touch(context.get(), url, last_access_time,
                last_modified_time));
        EXPECT_TRUE(change_observer()->HasNoChange());
        context.reset(NewContext(NULL));
        EXPECT_EQ(base::File::FILE_OK,
            ofu()->GetFileInfo(context.get(), url, &file_info, &local_path));
        EXPECT_EQ(file_info.last_modified.ToTimeT(), last_modified_time.ToTimeT());
        if (is_file) // Directories in OFU don't support atime.
            EXPECT_EQ(file_info.last_accessed.ToTimeT(), last_access_time.ToTimeT());
    }

    void TestCopyInForeignFileHelper(bool overwrite)
    {
        base::ScopedTempDir source_dir;
        ASSERT_TRUE(source_dir.CreateUniqueTempDir());
        base::FilePath root_file_path = source_dir.GetPath();
        base::FilePath src_file_path = root_file_path.AppendASCII("file_name");
        FileSystemURL dest_url = CreateURLFromUTF8("new file");
        int64_t src_file_length = 87;

        base::File file(src_file_path,
            base::File::FLAG_CREATE | base::File::FLAG_WRITE);
        ASSERT_TRUE(file.IsValid());
        EXPECT_TRUE(file.created());
        ASSERT_TRUE(file.SetLength(src_file_length));
        file.Close();

        std::unique_ptr<FileSystemOperationContext> context;

        if (overwrite) {
            context.reset(NewContext(NULL));
            bool created = false;
            EXPECT_EQ(base::File::FILE_OK,
                ofu()->EnsureFileExists(context.get(), dest_url, &created));
            EXPECT_TRUE(created);

            // We must have observed one (and only one) create_file_count.
            EXPECT_EQ(1, change_observer()->get_and_reset_create_file_count());
            EXPECT_TRUE(change_observer()->HasNoChange());
        }

        const int64_t path_cost = ObfuscatedFileUtil::ComputeFilePathCost(dest_url.path());
        if (!overwrite) {
            // Verify that file creation requires sufficient quota for the path.
            context.reset(NewContext(NULL));
            context->set_allowed_bytes_growth(path_cost + src_file_length - 1);
            EXPECT_EQ(base::File::FILE_ERROR_NO_SPACE,
                ofu()->CopyInForeignFile(context.get(),
                    src_file_path, dest_url));
        }

        context.reset(NewContext(NULL));
        context->set_allowed_bytes_growth(path_cost + src_file_length);
        EXPECT_EQ(base::File::FILE_OK,
            ofu()->CopyInForeignFile(context.get(),
                src_file_path, dest_url));

        EXPECT_TRUE(PathExists(dest_url));
        EXPECT_FALSE(DirectoryExists(dest_url));

        context.reset(NewContext(NULL));
        base::File::Info file_info;
        base::FilePath data_path;
        EXPECT_EQ(base::File::FILE_OK,
            ofu()->GetFileInfo(context.get(), dest_url, &file_info,
                &data_path));
        EXPECT_NE(data_path, src_file_path);
        EXPECT_TRUE(FileExists(data_path));
        EXPECT_EQ(src_file_length, GetSize(data_path));

        EXPECT_EQ(base::File::FILE_OK,
            ofu()->DeleteFile(context.get(), dest_url));
    }

    void ClearTimestamp(const FileSystemURL& url)
    {
        std::unique_ptr<FileSystemOperationContext> context(NewContext(NULL));
        EXPECT_EQ(base::File::FILE_OK,
            ofu()->Touch(context.get(), url, base::Time(), base::Time()));
        EXPECT_EQ(base::Time(), GetModifiedTime(url));
    }

    base::Time GetModifiedTime(const FileSystemURL& url)
    {
        std::unique_ptr<FileSystemOperationContext> context(NewContext(NULL));
        base::FilePath data_path;
        base::File::Info file_info;
        context.reset(NewContext(NULL));
        EXPECT_EQ(base::File::FILE_OK,
            ofu()->GetFileInfo(context.get(), url, &file_info, &data_path));
        EXPECT_TRUE(change_observer()->HasNoChange());
        return file_info.last_modified;
    }

    void TestDirectoryTimestampHelper(const FileSystemURL& base_dir,
        bool copy,
        bool overwrite)
    {
        std::unique_ptr<FileSystemOperationContext> context;
        const FileSystemURL src_dir_url(
            FileSystemURLAppendUTF8(base_dir, "foo_dir"));
        const FileSystemURL dest_dir_url(
            FileSystemURLAppendUTF8(base_dir, "bar_dir"));

        const FileSystemURL src_file_url(
            FileSystemURLAppendUTF8(src_dir_url, "hoge"));
        const FileSystemURL dest_file_url(
            FileSystemURLAppendUTF8(dest_dir_url, "fuga"));

        context.reset(NewContext(NULL));
        EXPECT_EQ(base::File::FILE_OK,
            ofu()->CreateDirectory(context.get(), src_dir_url, true, true));
        context.reset(NewContext(NULL));
        EXPECT_EQ(base::File::FILE_OK,
            ofu()->CreateDirectory(context.get(), dest_dir_url, true, true));

        bool created = false;
        context.reset(NewContext(NULL));
        EXPECT_EQ(base::File::FILE_OK,
            ofu()->EnsureFileExists(context.get(), src_file_url, &created));
        if (overwrite) {
            context.reset(NewContext(NULL));
            EXPECT_EQ(base::File::FILE_OK,
                ofu()->EnsureFileExists(context.get(),
                    dest_file_url, &created));
        }

        ClearTimestamp(src_dir_url);
        ClearTimestamp(dest_dir_url);
        context.reset(NewContext(NULL));
        EXPECT_EQ(base::File::FILE_OK,
            ofu()->CopyOrMoveFile(context.get(),
                src_file_url, dest_file_url,
                FileSystemOperation::OPTION_NONE,
                copy));
        if (copy)
            EXPECT_EQ(base::Time(), GetModifiedTime(src_dir_url));
        else
            EXPECT_NE(base::Time(), GetModifiedTime(src_dir_url));
        EXPECT_NE(base::Time(), GetModifiedTime(dest_dir_url));
    }

    void MaybeDropDatabasesAliveCaseTestBody()
    {
        std::unique_ptr<ObfuscatedFileUtil> file_util = CreateObfuscatedFileUtil(NULL);
        file_util->InitOriginDatabase(GURL(), true /*create*/);
        ASSERT_TRUE(file_util->origin_database_ != NULL);

        // Callback to Drop DB is called while ObfuscatedFileUtilTest is
        // still alive.
        file_util->db_flush_delay_seconds_ = 0;
        file_util->MarkUsed();
        base::RunLoop().RunUntilIdle();

        ASSERT_TRUE(file_util->origin_database_ == NULL);
    }

    void MaybeDropDatabasesAlreadyDeletedCaseTestBody()
    {
        // Run message loop after OFU is already deleted to make sure callback
        // doesn't cause a crash for use after free.
        {
            std::unique_ptr<ObfuscatedFileUtil> file_util = CreateObfuscatedFileUtil(NULL);
            file_util->InitOriginDatabase(GURL(), true /*create*/);
            file_util->db_flush_delay_seconds_ = 0;
            file_util->MarkUsed();
        }

        // At this point the callback is still in the message queue but OFU is gone.
        base::RunLoop().RunUntilIdle();
    }

    void DestroyDirectoryDatabase_IsolatedTestBody()
    {
        storage_policy_->AddIsolated(origin_);
        std::unique_ptr<ObfuscatedFileUtil> file_util = CreateObfuscatedFileUtil(storage_policy_.get());
        const FileSystemURL url = FileSystemURL::CreateForTest(
            origin_, kFileSystemTypePersistent, base::FilePath());

        // Create DirectoryDatabase for isolated origin.
        SandboxDirectoryDatabase* db = file_util->GetDirectoryDatabase(url, true /* create */);
        ASSERT_TRUE(db != NULL);

        // Destory it.
        file_util->DestroyDirectoryDatabase(
            url.origin(), GetTypeString(url.type()));
        ASSERT_TRUE(file_util->directories_.empty());
    }

    void GetDirectoryDatabase_IsolatedTestBody()
    {
        storage_policy_->AddIsolated(origin_);
        std::unique_ptr<ObfuscatedFileUtil> file_util = CreateObfuscatedFileUtil(storage_policy_.get());
        const FileSystemURL url = FileSystemURL::CreateForTest(
            origin_, kFileSystemTypePersistent, base::FilePath());

        // Create DirectoryDatabase for isolated origin.
        SandboxDirectoryDatabase* db = file_util->GetDirectoryDatabase(url, true /* create */);
        ASSERT_TRUE(db != NULL);
        ASSERT_EQ(1U, file_util->directories_.size());

        // Remove isolated.
        storage_policy_->RemoveIsolated(url.origin());

        // This should still get the same database.
        SandboxDirectoryDatabase* db2 = file_util->GetDirectoryDatabase(url, false /* create */);
        ASSERT_EQ(db, db2);
    }

    void MigrationBackFromIsolatedTestBody()
    {
        std::string kFakeDirectoryData("0123456789");
        base::FilePath old_directory_db_path;

        // Initialize the directory with one origin using
        // SandboxIsolatedOriginDatabase.
        {
            std::string origin_string = storage::GetIdentifierFromOrigin(origin_);
            SandboxIsolatedOriginDatabase database_old(
                origin_string, data_dir_path(),
                base::FilePath(
                    SandboxIsolatedOriginDatabase::kObsoleteOriginDirectory));
            base::FilePath path;
            EXPECT_TRUE(database_old.GetPathForOrigin(origin_string, &path));
            EXPECT_FALSE(path.empty());

            // Populate the origin directory with some fake data.
            old_directory_db_path = data_dir_path().Append(path);
            ASSERT_TRUE(base::CreateDirectory(old_directory_db_path));
            EXPECT_EQ(static_cast<int>(kFakeDirectoryData.size()),
                base::WriteFile(old_directory_db_path.AppendASCII("dummy"),
                    kFakeDirectoryData.data(),
                    kFakeDirectoryData.size()));
        }

        storage_policy_->AddIsolated(origin_);
        std::unique_ptr<ObfuscatedFileUtil> file_util = CreateObfuscatedFileUtil(storage_policy_.get());
        base::File::Error error = base::File::FILE_ERROR_FAILED;
        base::FilePath origin_directory = file_util->GetDirectoryForOrigin(
            origin_, true /* create */, &error);
        EXPECT_EQ(base::File::FILE_OK, error);

        // The database is migrated from the old one.
        EXPECT_TRUE(base::DirectoryExists(origin_directory));
        EXPECT_FALSE(base::DirectoryExists(old_directory_db_path));

        // Check we see the same contents in the new origin directory.
        std::string origin_db_data;
        EXPECT_TRUE(base::PathExists(origin_directory.AppendASCII("dummy")));
        EXPECT_TRUE(base::ReadFileToString(
            origin_directory.AppendASCII("dummy"), &origin_db_data));
        EXPECT_EQ(kFakeDirectoryData, origin_db_data);
    }

    int64_t ComputeCurrentUsage()
    {
        return sandbox_file_system_.ComputeCurrentOriginUsage() - sandbox_file_system_.ComputeCurrentDirectoryDatabaseUsage();
    }

    FileSystemContext* file_system_context()
    {
        return sandbox_file_system_.file_system_context();
    }

    const base::FilePath& data_dir_path() const { return data_dir_.GetPath(); }

protected:
    base::ScopedTempDir data_dir_;
    base::MessageLoopForIO message_loop_;
    scoped_refptr<MockSpecialStoragePolicy> storage_policy_;
    scoped_refptr<storage::QuotaManager> quota_manager_;
    scoped_refptr<FileSystemContext> file_system_context_;
    GURL origin_;
    storage::FileSystemType type_;
    SandboxFileSystemTestHelper sandbox_file_system_;
    storage::QuotaStatusCode quota_status_;
    int64_t usage_;
    storage::MockFileChangeObserver change_observer_;
    storage::ChangeObserverList change_observers_;
    base::WeakPtrFactory<ObfuscatedFileUtilTest> weak_factory_;

private:
    DISALLOW_COPY_AND_ASSIGN(ObfuscatedFileUtilTest);
};

TEST_F(ObfuscatedFileUtilTest, TestCreateAndDeleteFile)
{
    FileSystemURL url = CreateURLFromUTF8("fake/file");
    std::unique_ptr<FileSystemOperationContext> context(NewContext(NULL));
    int file_flags = base::File::FLAG_CREATE | base::File::FLAG_WRITE;

    base::File file = ofu()->CreateOrOpen(context.get(), url, file_flags);
    EXPECT_FALSE(file.IsValid());
    EXPECT_EQ(base::File::FILE_ERROR_NOT_FOUND, file.error_details());

    context.reset(NewContext(NULL));
    EXPECT_EQ(base::File::FILE_ERROR_NOT_FOUND,
        ofu()->DeleteFile(context.get(), url));

    url = CreateURLFromUTF8("test file");

    EXPECT_TRUE(change_observer()->HasNoChange());

    // Verify that file creation requires sufficient quota for the path.
    context.reset(NewContext(NULL));
    context->set_allowed_bytes_growth(
        ObfuscatedFileUtil::ComputeFilePathCost(url.path()) - 1);
    file = ofu()->CreateOrOpen(context.get(), url, file_flags);
    EXPECT_FALSE(file.IsValid());
    ASSERT_EQ(base::File::FILE_ERROR_NO_SPACE, file.error_details());

    context.reset(NewContext(NULL));
    context->set_allowed_bytes_growth(
        ObfuscatedFileUtil::ComputeFilePathCost(url.path()));
    file = ofu()->CreateOrOpen(context.get(), url, file_flags);
    EXPECT_TRUE(file.IsValid());
    ASSERT_TRUE(file.created());
    EXPECT_EQ(1, change_observer()->get_and_reset_create_file_count());

    CheckFileAndCloseHandle(url, std::move(file));

    context.reset(NewContext(NULL));
    base::FilePath local_path;
    EXPECT_EQ(base::File::FILE_OK,
        ofu()->GetLocalFilePath(context.get(), url, &local_path));
    EXPECT_TRUE(base::PathExists(local_path));

    // Verify that deleting a file isn't stopped by zero quota, and that it frees
    // up quote from its path.
    context.reset(NewContext(NULL));
    context->set_allowed_bytes_growth(0);
    EXPECT_EQ(base::File::FILE_OK, ofu()->DeleteFile(context.get(), url));
    EXPECT_EQ(1, change_observer()->get_and_reset_remove_file_count());
    EXPECT_FALSE(base::PathExists(local_path));
    EXPECT_EQ(ObfuscatedFileUtil::ComputeFilePathCost(url.path()),
        context->allowed_bytes_growth());

    context.reset(NewContext(NULL));
    bool exclusive = true;
    bool recursive = true;
    FileSystemURL directory_url = CreateURLFromUTF8(
        "series/of/directories");
    url = FileSystemURLAppendUTF8(directory_url, "file name");
    EXPECT_EQ(base::File::FILE_OK,
        ofu()->CreateDirectory(context.get(), directory_url, exclusive,
            recursive));
    // The oepration created 3 directories recursively.
    EXPECT_EQ(3, change_observer()->get_and_reset_create_directory_count());

    context.reset(NewContext(NULL));
    file = ofu()->CreateOrOpen(context.get(), url, file_flags);
    ASSERT_TRUE(file.IsValid());
    ASSERT_TRUE(file.created());
    EXPECT_EQ(1, change_observer()->get_and_reset_create_file_count());

    CheckFileAndCloseHandle(url, std::move(file));

    context.reset(NewContext(NULL));
    EXPECT_EQ(base::File::FILE_OK,
        ofu()->GetLocalFilePath(context.get(), url, &local_path));
    EXPECT_TRUE(base::PathExists(local_path));

    context.reset(NewContext(NULL));
    EXPECT_EQ(base::File::FILE_OK, ofu()->DeleteFile(context.get(), url));
    EXPECT_EQ(1, change_observer()->get_and_reset_remove_file_count());
    EXPECT_FALSE(base::PathExists(local_path));

    // Make sure we have no unexpected changes.
    EXPECT_TRUE(change_observer()->HasNoChange());
}

TEST_F(ObfuscatedFileUtilTest, TestTruncate)
{
    bool created = false;
    FileSystemURL url = CreateURLFromUTF8("file");
    std::unique_ptr<FileSystemOperationContext> context(NewContext(NULL));

    EXPECT_EQ(base::File::FILE_ERROR_NOT_FOUND,
        ofu()->Truncate(context.get(), url, 4));

    context.reset(NewContext(NULL));
    ASSERT_EQ(base::File::FILE_OK,
        ofu()->EnsureFileExists(context.get(), url, &created));
    ASSERT_TRUE(created);
    EXPECT_EQ(1, change_observer()->get_and_reset_create_file_count());

    context.reset(NewContext(NULL));
    base::FilePath local_path;
    EXPECT_EQ(base::File::FILE_OK,
        ofu()->GetLocalFilePath(context.get(), url, &local_path));
    EXPECT_EQ(0, GetSize(local_path));

    context.reset(NewContext(NULL));
    EXPECT_EQ(base::File::FILE_OK, ofu()->Truncate(context.get(), url, 10));
    EXPECT_EQ(1, change_observer()->get_and_reset_modify_file_count());
    EXPECT_EQ(10, GetSize(local_path));

    context.reset(NewContext(NULL));
    EXPECT_EQ(base::File::FILE_OK, ofu()->Truncate(context.get(), url, 1));
    EXPECT_EQ(1, GetSize(local_path));
    EXPECT_EQ(1, change_observer()->get_and_reset_modify_file_count());

    EXPECT_FALSE(DirectoryExists(url));
    EXPECT_TRUE(PathExists(url));

    // Make sure we have no unexpected changes.
    EXPECT_TRUE(change_observer()->HasNoChange());
}

TEST_F(ObfuscatedFileUtilTest, TestQuotaOnTruncation)
{
    bool created = false;
    FileSystemURL url = CreateURLFromUTF8("file");

    ASSERT_EQ(base::File::FILE_OK,
        ofu()->EnsureFileExists(
            AllowUsageIncrease(PathCost(url))->context(),
            url, &created));
    ASSERT_TRUE(created);
    ASSERT_EQ(0, ComputeTotalFileSize());

    ASSERT_EQ(base::File::FILE_OK,
        ofu()->Truncate(AllowUsageIncrease(1020)->context(), url, 1020));
    ASSERT_EQ(1020, ComputeTotalFileSize());

    ASSERT_EQ(base::File::FILE_OK,
        ofu()->Truncate(AllowUsageIncrease(-1020)->context(), url, 0));
    ASSERT_EQ(0, ComputeTotalFileSize());

    EXPECT_EQ(base::File::FILE_ERROR_NO_SPACE,
        ofu()->Truncate(DisallowUsageIncrease(1021)->context(),
            url, 1021));
    ASSERT_EQ(0, ComputeTotalFileSize());

    EXPECT_EQ(base::File::FILE_OK,
        ofu()->Truncate(AllowUsageIncrease(1020)->context(), url, 1020));
    ASSERT_EQ(1020, ComputeTotalFileSize());

    EXPECT_EQ(base::File::FILE_OK,
        ofu()->Truncate(AllowUsageIncrease(0)->context(), url, 1020));
    ASSERT_EQ(1020, ComputeTotalFileSize());

    // quota exceeded
    {
        std::unique_ptr<UsageVerifyHelper> helper = AllowUsageIncrease(-1);
        helper->context()->set_allowed_bytes_growth(
            helper->context()->allowed_bytes_growth() - 1);
        EXPECT_EQ(base::File::FILE_OK,
            ofu()->Truncate(helper->context(), url, 1019));
        ASSERT_EQ(1019, ComputeTotalFileSize());
    }

    // Delete backing file to make following truncation fail.
    base::FilePath local_path;
    ASSERT_EQ(base::File::FILE_OK,
        ofu()->GetLocalFilePath(UnlimitedContext().get(), url,
            &local_path));
    ASSERT_FALSE(local_path.empty());
    ASSERT_TRUE(base::DeleteFile(local_path, false));

    EXPECT_EQ(base::File::FILE_ERROR_NOT_FOUND,
        ofu()->Truncate(LimitedContext(1234).get(), url, 1234));
    ASSERT_EQ(0, ComputeTotalFileSize());
}

TEST_F(ObfuscatedFileUtilTest, TestEnsureFileExists)
{
    FileSystemURL url = CreateURLFromUTF8("fake/file");
    bool created = false;
    std::unique_ptr<FileSystemOperationContext> context(NewContext(NULL));
    EXPECT_EQ(base::File::FILE_ERROR_NOT_FOUND,
        ofu()->EnsureFileExists(context.get(), url, &created));
    EXPECT_TRUE(change_observer()->HasNoChange());

    // Verify that file creation requires sufficient quota for the path.
    context.reset(NewContext(NULL));
    url = CreateURLFromUTF8("test file");
    created = false;
    context->set_allowed_bytes_growth(
        ObfuscatedFileUtil::ComputeFilePathCost(url.path()) - 1);
    ASSERT_EQ(base::File::FILE_ERROR_NO_SPACE,
        ofu()->EnsureFileExists(context.get(), url, &created));
    ASSERT_FALSE(created);
    EXPECT_TRUE(change_observer()->HasNoChange());

    context.reset(NewContext(NULL));
    context->set_allowed_bytes_growth(
        ObfuscatedFileUtil::ComputeFilePathCost(url.path()));
    ASSERT_EQ(base::File::FILE_OK,
        ofu()->EnsureFileExists(context.get(), url, &created));
    ASSERT_TRUE(created);
    EXPECT_EQ(1, change_observer()->get_and_reset_create_file_count());

    CheckFileAndCloseHandle(url, base::File());

    context.reset(NewContext(NULL));
    ASSERT_EQ(base::File::FILE_OK,
        ofu()->EnsureFileExists(context.get(), url, &created));
    ASSERT_FALSE(created);
    EXPECT_TRUE(change_observer()->HasNoChange());

    // Also test in a subdirectory.
    url = CreateURLFromUTF8("path/to/file.txt");
    context.reset(NewContext(NULL));
    bool exclusive = true;
    bool recursive = true;
    EXPECT_EQ(base::File::FILE_OK,
        ofu()->CreateDirectory(context.get(), FileSystemURLDirName(url),
            exclusive, recursive));
    // 2 directories: path/ and path/to.
    EXPECT_EQ(2, change_observer()->get_and_reset_create_directory_count());

    context.reset(NewContext(NULL));
    ASSERT_EQ(base::File::FILE_OK,
        ofu()->EnsureFileExists(context.get(), url, &created));
    ASSERT_TRUE(created);
    EXPECT_FALSE(DirectoryExists(url));
    EXPECT_TRUE(PathExists(url));
    EXPECT_TRUE(change_observer()->HasNoChange());
}

TEST_F(ObfuscatedFileUtilTest, TestDirectoryOps)
{
    std::unique_ptr<FileSystemOperationContext> context(NewContext(NULL));

    bool exclusive = false;
    bool recursive = false;
    FileSystemURL url = CreateURLFromUTF8("foo/bar");
    EXPECT_EQ(base::File::FILE_ERROR_NOT_FOUND,
        ofu()->CreateDirectory(context.get(), url, exclusive, recursive));

    context.reset(NewContext(NULL));
    EXPECT_EQ(base::File::FILE_ERROR_NOT_FOUND,
        ofu()->DeleteDirectory(context.get(), url));

    FileSystemURL root = CreateURLFromUTF8(std::string());
    EXPECT_FALSE(DirectoryExists(url));
    EXPECT_FALSE(PathExists(url));
    context.reset(NewContext(NULL));
    EXPECT_TRUE(ofu()->IsDirectoryEmpty(context.get(), root));

    context.reset(NewContext(NULL));
    exclusive = false;
    recursive = true;
    EXPECT_EQ(base::File::FILE_OK,
        ofu()->CreateDirectory(context.get(), url, exclusive, recursive));
    EXPECT_EQ(2, change_observer()->get_and_reset_create_directory_count());

    EXPECT_TRUE(DirectoryExists(url));
    EXPECT_TRUE(PathExists(url));

    context.reset(NewContext(NULL));
    EXPECT_FALSE(ofu()->IsDirectoryEmpty(context.get(), root));
    EXPECT_TRUE(DirectoryExists(FileSystemURLDirName(url)));

    context.reset(NewContext(NULL));
    EXPECT_FALSE(ofu()->IsDirectoryEmpty(context.get(),
        FileSystemURLDirName(url)));

    // Can't remove a non-empty directory.
    context.reset(NewContext(NULL));
    EXPECT_EQ(base::File::FILE_ERROR_NOT_EMPTY,
        ofu()->DeleteDirectory(context.get(),
            FileSystemURLDirName(url)));
    EXPECT_TRUE(change_observer()->HasNoChange());

    base::File::Info file_info;
    base::FilePath local_path;
    EXPECT_EQ(base::File::FILE_OK,
        ofu()->GetFileInfo(context.get(), url, &file_info, &local_path));
    EXPECT_TRUE(local_path.empty());
    EXPECT_TRUE(file_info.is_directory);
    EXPECT_FALSE(file_info.is_symbolic_link);

    // Same create again should succeed, since exclusive is false.
    context.reset(NewContext(NULL));
    EXPECT_EQ(base::File::FILE_OK,
        ofu()->CreateDirectory(context.get(), url, exclusive, recursive));
    EXPECT_TRUE(change_observer()->HasNoChange());

    exclusive = true;
    recursive = true;
    context.reset(NewContext(NULL));
    EXPECT_EQ(base::File::FILE_ERROR_EXISTS,
        ofu()->CreateDirectory(context.get(), url, exclusive, recursive));
    EXPECT_TRUE(change_observer()->HasNoChange());

    // Verify that deleting a directory isn't stopped by zero quota, and that it
    // frees up quota from its path.
    context.reset(NewContext(NULL));
    context->set_allowed_bytes_growth(0);
    EXPECT_EQ(base::File::FILE_OK, ofu()->DeleteDirectory(context.get(), url));
    EXPECT_EQ(1, change_observer()->get_and_reset_remove_directory_count());
    EXPECT_EQ(ObfuscatedFileUtil::ComputeFilePathCost(url.path()),
        context->allowed_bytes_growth());

    url = CreateURLFromUTF8("foo/bop");

    EXPECT_FALSE(DirectoryExists(url));
    EXPECT_FALSE(PathExists(url));

    context.reset(NewContext(NULL));
    EXPECT_TRUE(ofu()->IsDirectoryEmpty(context.get(), url));
    EXPECT_EQ(base::File::FILE_ERROR_NOT_FOUND,
        ofu()->GetFileInfo(context.get(), url, &file_info, &local_path));

    // Verify that file creation requires sufficient quota for the path.
    exclusive = true;
    recursive = false;
    context.reset(NewContext(NULL));
    context->set_allowed_bytes_growth(
        ObfuscatedFileUtil::ComputeFilePathCost(url.path()) - 1);
    EXPECT_EQ(base::File::FILE_ERROR_NO_SPACE,
        ofu()->CreateDirectory(context.get(), url, exclusive, recursive));
    EXPECT_TRUE(change_observer()->HasNoChange());

    context.reset(NewContext(NULL));
    context->set_allowed_bytes_growth(
        ObfuscatedFileUtil::ComputeFilePathCost(url.path()));
    EXPECT_EQ(base::File::FILE_OK,
        ofu()->CreateDirectory(context.get(), url, exclusive, recursive));
    EXPECT_EQ(1, change_observer()->get_and_reset_create_directory_count());

    EXPECT_TRUE(DirectoryExists(url));
    EXPECT_TRUE(PathExists(url));

    exclusive = true;
    recursive = false;
    context.reset(NewContext(NULL));
    EXPECT_EQ(base::File::FILE_ERROR_EXISTS,
        ofu()->CreateDirectory(context.get(), url, exclusive, recursive));
    EXPECT_TRUE(change_observer()->HasNoChange());

    exclusive = true;
    recursive = false;
    url = CreateURLFromUTF8("foo");
    context.reset(NewContext(NULL));
    EXPECT_EQ(base::File::FILE_ERROR_EXISTS,
        ofu()->CreateDirectory(context.get(), url, exclusive, recursive));
    EXPECT_TRUE(change_observer()->HasNoChange());

    url = CreateURLFromUTF8("blah");

    EXPECT_FALSE(DirectoryExists(url));
    EXPECT_FALSE(PathExists(url));

    exclusive = true;
    recursive = false;
    context.reset(NewContext(NULL));
    EXPECT_EQ(base::File::FILE_OK,
        ofu()->CreateDirectory(context.get(), url, exclusive, recursive));
    EXPECT_EQ(1, change_observer()->get_and_reset_create_directory_count());

    EXPECT_TRUE(DirectoryExists(url));
    EXPECT_TRUE(PathExists(url));

    exclusive = true;
    recursive = false;
    context.reset(NewContext(NULL));
    EXPECT_EQ(base::File::FILE_ERROR_EXISTS,
        ofu()->CreateDirectory(context.get(), url, exclusive, recursive));
    EXPECT_TRUE(change_observer()->HasNoChange());
}

TEST_F(ObfuscatedFileUtilTest, TestReadDirectory)
{
    std::unique_ptr<FileSystemOperationContext> context(NewContext(NULL));
    bool exclusive = true;
    bool recursive = true;
    FileSystemURL url = CreateURLFromUTF8("directory/to/use");
    EXPECT_EQ(base::File::FILE_OK,
        ofu()->CreateDirectory(context.get(), url, exclusive, recursive));
    TestReadDirectoryHelper(url);
}

TEST_F(ObfuscatedFileUtilTest, TestReadRootWithSlash)
{
    TestReadDirectoryHelper(CreateURLFromUTF8(std::string()));
}

TEST_F(ObfuscatedFileUtilTest, TestReadRootWithEmptyString)
{
    TestReadDirectoryHelper(CreateURLFromUTF8("/"));
}

TEST_F(ObfuscatedFileUtilTest, TestReadDirectoryOnFile)
{
    FileSystemURL url = CreateURLFromUTF8("file");
    std::unique_ptr<FileSystemOperationContext> context(NewContext(NULL));

    bool created = false;
    ASSERT_EQ(base::File::FILE_OK,
        ofu()->EnsureFileExists(context.get(), url, &created));
    ASSERT_TRUE(created);

    std::vector<storage::DirectoryEntry> entries;
    EXPECT_EQ(base::File::FILE_ERROR_NOT_A_DIRECTORY,
        AsyncFileTestHelper::ReadDirectory(file_system_context(), url,
            &entries));

    EXPECT_TRUE(ofu()->IsDirectoryEmpty(context.get(), url));
}

TEST_F(ObfuscatedFileUtilTest, TestTouch)
{
    FileSystemURL url = CreateURLFromUTF8("file");
    std::unique_ptr<FileSystemOperationContext> context(NewContext(NULL));

    base::Time last_access_time = base::Time::Now();
    base::Time last_modified_time = base::Time::Now();

    // It's not there yet.
    EXPECT_EQ(base::File::FILE_ERROR_NOT_FOUND,
        ofu()->Touch(context.get(), url, last_access_time,
            last_modified_time));

    // OK, now create it.
    context.reset(NewContext(NULL));
    bool created = false;
    ASSERT_EQ(base::File::FILE_OK,
        ofu()->EnsureFileExists(context.get(), url, &created));
    ASSERT_TRUE(created);
    TestTouchHelper(url, true);

    // Now test a directory:
    context.reset(NewContext(NULL));
    bool exclusive = true;
    bool recursive = false;
    url = CreateURLFromUTF8("dir");
    ASSERT_EQ(base::File::FILE_OK,
        ofu()->CreateDirectory(context.get(), url, exclusive, recursive));
    TestTouchHelper(url, false);
}

TEST_F(ObfuscatedFileUtilTest, TestPathQuotas)
{
    FileSystemURL url = CreateURLFromUTF8("fake/file");
    std::unique_ptr<FileSystemOperationContext> context(NewContext(NULL));

    url = CreateURLFromUTF8("file name");
    context->set_allowed_bytes_growth(5);
    bool created = false;
    EXPECT_EQ(base::File::FILE_ERROR_NO_SPACE,
        ofu()->EnsureFileExists(context.get(), url, &created));
    EXPECT_FALSE(created);
    context->set_allowed_bytes_growth(1024);
    EXPECT_EQ(base::File::FILE_OK,
        ofu()->EnsureFileExists(context.get(), url, &created));
    EXPECT_TRUE(created);
    int64_t path_cost = ObfuscatedFileUtil::ComputeFilePathCost(url.path());
    EXPECT_EQ(1024 - path_cost, context->allowed_bytes_growth());

    context->set_allowed_bytes_growth(1024);
    bool exclusive = true;
    bool recursive = true;
    url = CreateURLFromUTF8("directory/to/use");
    std::vector<base::FilePath::StringType> components;
    url.path().GetComponents(&components);
    path_cost = 0;
    typedef std::vector<base::FilePath::StringType>::iterator iterator;
    for (iterator iter = components.begin();
         iter != components.end(); ++iter) {
        path_cost += ObfuscatedFileUtil::ComputeFilePathCost(
            base::FilePath(*iter));
    }
    context.reset(NewContext(NULL));
    context->set_allowed_bytes_growth(1024);
    EXPECT_EQ(base::File::FILE_OK,
        ofu()->CreateDirectory(context.get(), url, exclusive, recursive));
    EXPECT_EQ(1024 - path_cost, context->allowed_bytes_growth());
}

TEST_F(ObfuscatedFileUtilTest, TestCopyOrMoveFileNotFound)
{
    FileSystemURL source_url = CreateURLFromUTF8("path0.txt");
    FileSystemURL dest_url = CreateURLFromUTF8("path1.txt");
    std::unique_ptr<FileSystemOperationContext> context(NewContext(NULL));

    bool is_copy_not_move = false;
    EXPECT_EQ(base::File::FILE_ERROR_NOT_FOUND,
        ofu()->CopyOrMoveFile(context.get(), source_url, dest_url,
            FileSystemOperation::OPTION_NONE,
            is_copy_not_move));
    EXPECT_TRUE(change_observer()->HasNoChange());
    context.reset(NewContext(NULL));
    is_copy_not_move = true;
    EXPECT_EQ(base::File::FILE_ERROR_NOT_FOUND,
        ofu()->CopyOrMoveFile(context.get(), source_url, dest_url,
            FileSystemOperation::OPTION_NONE,
            is_copy_not_move));
    EXPECT_TRUE(change_observer()->HasNoChange());
    source_url = CreateURLFromUTF8("dir/dir/file");
    bool exclusive = true;
    bool recursive = true;
    context.reset(NewContext(NULL));
    ASSERT_EQ(base::File::FILE_OK,
        ofu()->CreateDirectory(context.get(),
            FileSystemURLDirName(source_url),
            exclusive, recursive));
    EXPECT_EQ(2, change_observer()->get_and_reset_create_directory_count());
    is_copy_not_move = false;
    EXPECT_EQ(base::File::FILE_ERROR_NOT_FOUND,
        ofu()->CopyOrMoveFile(context.get(), source_url, dest_url,
            FileSystemOperation::OPTION_NONE,
            is_copy_not_move));
    EXPECT_TRUE(change_observer()->HasNoChange());
    context.reset(NewContext(NULL));
    is_copy_not_move = true;
    EXPECT_EQ(base::File::FILE_ERROR_NOT_FOUND,
        ofu()->CopyOrMoveFile(context.get(), source_url, dest_url,
            FileSystemOperation::OPTION_NONE,
            is_copy_not_move));
    EXPECT_TRUE(change_observer()->HasNoChange());
}

TEST_F(ObfuscatedFileUtilTest, TestCopyOrMoveFileSuccess)
{
    const int64_t kSourceLength = 5;
    const int64_t kDestLength = 50;

    for (size_t i = 0; i < arraysize(kCopyMoveTestCases); ++i) {
        SCOPED_TRACE(testing::Message() << "kCopyMoveTestCase " << i);
        const CopyMoveTestCaseRecord& test_case = kCopyMoveTestCases[i];
        SCOPED_TRACE(testing::Message() << "\t is_copy_not_move " << test_case.is_copy_not_move);
        SCOPED_TRACE(testing::Message() << "\t source_path " << test_case.source_path);
        SCOPED_TRACE(testing::Message() << "\t dest_path " << test_case.dest_path);
        SCOPED_TRACE(testing::Message() << "\t cause_overwrite " << test_case.cause_overwrite);
        std::unique_ptr<FileSystemOperationContext> context(NewContext(NULL));

        bool exclusive = false;
        bool recursive = true;
        FileSystemURL source_url = CreateURLFromUTF8(test_case.source_path);
        FileSystemURL dest_url = CreateURLFromUTF8(test_case.dest_path);

        context.reset(NewContext(NULL));
        ASSERT_EQ(base::File::FILE_OK,
            ofu()->CreateDirectory(context.get(),
                FileSystemURLDirName(source_url),
                exclusive, recursive));
        context.reset(NewContext(NULL));
        ASSERT_EQ(base::File::FILE_OK,
            ofu()->CreateDirectory(context.get(),
                FileSystemURLDirName(dest_url),
                exclusive, recursive));

        bool created = false;
        context.reset(NewContext(NULL));
        ASSERT_EQ(base::File::FILE_OK,
            ofu()->EnsureFileExists(context.get(), source_url, &created));
        ASSERT_TRUE(created);
        context.reset(NewContext(NULL));
        ASSERT_EQ(base::File::FILE_OK,
            ofu()->Truncate(context.get(), source_url, kSourceLength));

        if (test_case.cause_overwrite) {
            context.reset(NewContext(NULL));
            created = false;
            ASSERT_EQ(base::File::FILE_OK,
                ofu()->EnsureFileExists(context.get(), dest_url, &created));
            ASSERT_TRUE(created);
            context.reset(NewContext(NULL));
            ASSERT_EQ(base::File::FILE_OK,
                ofu()->Truncate(context.get(), dest_url, kDestLength));
        }

        context.reset(NewContext(NULL));
        EXPECT_EQ(base::File::FILE_OK,
            ofu()->CopyOrMoveFile(context.get(), source_url, dest_url,
                FileSystemOperation::OPTION_NONE,
                test_case.is_copy_not_move));

        if (test_case.is_copy_not_move) {
            base::File::Info file_info;
            base::FilePath local_path;
            context.reset(NewContext(NULL));
            EXPECT_EQ(base::File::FILE_OK,
                ofu()->GetFileInfo(context.get(), source_url, &file_info,
                    &local_path));
            EXPECT_EQ(kSourceLength, file_info.size);
            EXPECT_EQ(base::File::FILE_OK,
                ofu()->DeleteFile(context.get(), source_url));
        } else {
            base::File::Info file_info;
            base::FilePath local_path;
            context.reset(NewContext(NULL));
            EXPECT_EQ(base::File::FILE_ERROR_NOT_FOUND,
                ofu()->GetFileInfo(context.get(), source_url, &file_info,
                    &local_path));
        }
        base::File::Info file_info;
        base::FilePath local_path;
        EXPECT_EQ(base::File::FILE_OK,
            ofu()->GetFileInfo(context.get(), dest_url, &file_info,
                &local_path));
        EXPECT_EQ(kSourceLength, file_info.size);

        EXPECT_EQ(base::File::FILE_OK,
            ofu()->DeleteFile(context.get(), dest_url));
    }
}

TEST_F(ObfuscatedFileUtilTest, TestCopyPathQuotas)
{
    FileSystemURL src_url = CreateURLFromUTF8("src path");
    FileSystemURL dest_url = CreateURLFromUTF8("destination path");
    std::unique_ptr<FileSystemOperationContext> context(NewContext(NULL));
    bool created = false;
    ASSERT_EQ(base::File::FILE_OK,
        ofu()->EnsureFileExists(context.get(), src_url, &created));

    bool is_copy = true;
    // Copy, no overwrite.
    context->set_allowed_bytes_growth(
        ObfuscatedFileUtil::ComputeFilePathCost(dest_url.path()) - 1);
    EXPECT_EQ(base::File::FILE_ERROR_NO_SPACE,
        ofu()->CopyOrMoveFile(context.get(), src_url, dest_url,
            FileSystemOperation::OPTION_NONE, is_copy));
    context.reset(NewContext(NULL));
    context->set_allowed_bytes_growth(
        ObfuscatedFileUtil::ComputeFilePathCost(dest_url.path()));
    EXPECT_EQ(base::File::FILE_OK,
        ofu()->CopyOrMoveFile(context.get(), src_url, dest_url,
            FileSystemOperation::OPTION_NONE, is_copy));

    // Copy, with overwrite.
    context.reset(NewContext(NULL));
    context->set_allowed_bytes_growth(0);
    EXPECT_EQ(base::File::FILE_OK,
        ofu()->CopyOrMoveFile(context.get(), src_url, dest_url,
            FileSystemOperation::OPTION_NONE, is_copy));
}

TEST_F(ObfuscatedFileUtilTest, TestMovePathQuotasWithRename)
{
    FileSystemURL src_url = CreateURLFromUTF8("src path");
    FileSystemURL dest_url = CreateURLFromUTF8("destination path");
    std::unique_ptr<FileSystemOperationContext> context(NewContext(NULL));
    bool created = false;
    ASSERT_EQ(base::File::FILE_OK,
        ofu()->EnsureFileExists(context.get(), src_url, &created));

    bool is_copy = false;
    // Move, rename, no overwrite.
    context.reset(NewContext(NULL));
    context->set_allowed_bytes_growth(
        ObfuscatedFileUtil::ComputeFilePathCost(dest_url.path()) - ObfuscatedFileUtil::ComputeFilePathCost(src_url.path()) - 1);
    EXPECT_EQ(base::File::FILE_ERROR_NO_SPACE,
        ofu()->CopyOrMoveFile(context.get(), src_url, dest_url,
            FileSystemOperation::OPTION_NONE, is_copy));
    context.reset(NewContext(NULL));
    context->set_allowed_bytes_growth(
        ObfuscatedFileUtil::ComputeFilePathCost(dest_url.path()) - ObfuscatedFileUtil::ComputeFilePathCost(src_url.path()));
    EXPECT_EQ(base::File::FILE_OK,
        ofu()->CopyOrMoveFile(context.get(), src_url, dest_url,
            FileSystemOperation::OPTION_NONE, is_copy));

    context.reset(NewContext(NULL));
    ASSERT_EQ(base::File::FILE_OK,
        ofu()->EnsureFileExists(context.get(), src_url, &created));

    // Move, rename, with overwrite.
    context.reset(NewContext(NULL));
    context->set_allowed_bytes_growth(0);
    EXPECT_EQ(base::File::FILE_OK,
        ofu()->CopyOrMoveFile(context.get(), src_url, dest_url,
            FileSystemOperation::OPTION_NONE, is_copy));
}

TEST_F(ObfuscatedFileUtilTest, TestMovePathQuotasWithoutRename)
{
    FileSystemURL src_url = CreateURLFromUTF8("src path");
    std::unique_ptr<FileSystemOperationContext> context(NewContext(NULL));
    bool created = false;
    ASSERT_EQ(base::File::FILE_OK,
        ofu()->EnsureFileExists(context.get(), src_url, &created));

    bool exclusive = true;
    bool recursive = false;
    FileSystemURL dir_url = CreateURLFromUTF8("directory path");
    context.reset(NewContext(NULL));
    ASSERT_EQ(base::File::FILE_OK,
        ofu()->CreateDirectory(context.get(), dir_url, exclusive,
            recursive));

    FileSystemURL dest_url = FileSystemURLAppend(
        dir_url, src_url.path().value());

    bool is_copy = false;
    int64_t allowed_bytes_growth = -1000; // Over quota, this should still work.
    // Move, no rename, no overwrite.
    context.reset(NewContext(NULL));
    context->set_allowed_bytes_growth(allowed_bytes_growth);
    EXPECT_EQ(base::File::FILE_OK,
        ofu()->CopyOrMoveFile(context.get(), src_url, dest_url,
            FileSystemOperation::OPTION_NONE, is_copy));
    EXPECT_EQ(allowed_bytes_growth, context->allowed_bytes_growth());

    // Move, no rename, with overwrite.
    context.reset(NewContext(NULL));
    ASSERT_EQ(base::File::FILE_OK,
        ofu()->EnsureFileExists(context.get(), src_url, &created));
    context.reset(NewContext(NULL));
    context->set_allowed_bytes_growth(allowed_bytes_growth);
    EXPECT_EQ(base::File::FILE_OK,
        ofu()->CopyOrMoveFile(context.get(), src_url, dest_url,
            FileSystemOperation::OPTION_NONE, is_copy));
    EXPECT_EQ(
        allowed_bytes_growth + ObfuscatedFileUtil::ComputeFilePathCost(src_url.path()),
        context->allowed_bytes_growth());
}

TEST_F(ObfuscatedFileUtilTest, TestCopyInForeignFile)
{
    TestCopyInForeignFileHelper(false /* overwrite */);
    TestCopyInForeignFileHelper(true /* overwrite */);
}

TEST_F(ObfuscatedFileUtilTest, TestEnumerator)
{
    std::unique_ptr<FileSystemOperationContext> context(NewContext(NULL));
    FileSystemURL src_url = CreateURLFromUTF8("source dir");
    bool exclusive = true;
    bool recursive = false;
    ASSERT_EQ(base::File::FILE_OK,
        ofu()->CreateDirectory(context.get(), src_url, exclusive,
            recursive));

    std::set<base::FilePath::StringType> files;
    std::set<base::FilePath::StringType> directories;
    FillTestDirectory(src_url, &files, &directories);

    FileSystemURL dest_url = CreateURLFromUTF8("destination dir");

    EXPECT_FALSE(DirectoryExists(dest_url));
    ASSERT_EQ(base::File::FILE_OK,
        AsyncFileTestHelper::Copy(
            file_system_context(), src_url, dest_url));

    ValidateTestDirectory(dest_url, files, directories);
    EXPECT_TRUE(DirectoryExists(src_url));
    EXPECT_TRUE(DirectoryExists(dest_url));
    recursive = true;
    ASSERT_EQ(base::File::FILE_OK,
        AsyncFileTestHelper::Remove(
            file_system_context(), dest_url, recursive));
    EXPECT_FALSE(DirectoryExists(dest_url));
}

TEST_F(ObfuscatedFileUtilTest, TestOriginEnumerator)
{
    std::unique_ptr<ObfuscatedFileUtil::AbstractOriginEnumerator> enumerator(
        ofu()->CreateOriginEnumerator());
    // The test helper starts out with a single filesystem.
    EXPECT_TRUE(enumerator.get());
    EXPECT_EQ(origin(), enumerator->Next());
    ASSERT_TRUE(type() == kFileSystemTypeTemporary);
    EXPECT_TRUE(HasFileSystemType(enumerator.get(), kFileSystemTypeTemporary));
    EXPECT_FALSE(HasFileSystemType(enumerator.get(), kFileSystemTypePersistent));
    EXPECT_EQ(GURL(), enumerator->Next());
    EXPECT_FALSE(HasFileSystemType(enumerator.get(), kFileSystemTypeTemporary));
    EXPECT_FALSE(HasFileSystemType(enumerator.get(), kFileSystemTypePersistent));

    std::set<GURL> origins_expected;
    origins_expected.insert(origin());

    for (size_t i = 0; i < arraysize(kOriginEnumerationTestRecords); ++i) {
        SCOPED_TRACE(testing::Message() << "Validating kOriginEnumerationTestRecords " << i);
        const OriginEnumerationTestRecord& record = kOriginEnumerationTestRecords[i];
        GURL origin_url(record.origin_url);
        origins_expected.insert(origin_url);
        if (record.has_temporary) {
            std::unique_ptr<SandboxFileSystemTestHelper> file_system(
                NewFileSystem(origin_url, kFileSystemTypeTemporary));
            std::unique_ptr<FileSystemOperationContext> context(
                NewContext(file_system.get()));
            bool created = false;
            ASSERT_EQ(base::File::FILE_OK,
                ofu()->EnsureFileExists(
                    context.get(),
                    file_system->CreateURLFromUTF8("file"),
                    &created));
            EXPECT_TRUE(created);
        }
        if (record.has_persistent) {
            std::unique_ptr<SandboxFileSystemTestHelper> file_system(
                NewFileSystem(origin_url, kFileSystemTypePersistent));
            std::unique_ptr<FileSystemOperationContext> context(
                NewContext(file_system.get()));
            bool created = false;
            ASSERT_EQ(base::File::FILE_OK,
                ofu()->EnsureFileExists(
                    context.get(),
                    file_system->CreateURLFromUTF8("file"),
                    &created));
            EXPECT_TRUE(created);
        }
    }
    enumerator.reset(ofu()->CreateOriginEnumerator());
    EXPECT_TRUE(enumerator.get());
    std::set<GURL> origins_found;
    GURL origin_url;
    while (!(origin_url = enumerator->Next()).is_empty()) {
        origins_found.insert(origin_url);
        SCOPED_TRACE(testing::Message() << "Handling " << origin_url.spec());
        bool found = false;
        for (size_t i = 0; !found && i < arraysize(kOriginEnumerationTestRecords);
             ++i) {
            const OriginEnumerationTestRecord& record = kOriginEnumerationTestRecords[i];
            if (origin_url != record.origin_url)
                continue;
            found = true;
            EXPECT_EQ(record.has_temporary,
                HasFileSystemType(enumerator.get(), kFileSystemTypeTemporary));
            EXPECT_EQ(record.has_persistent,
                HasFileSystemType(enumerator.get(), kFileSystemTypePersistent));
        }
        // Deal with the default filesystem created by the test helper.
        if (!found && origin_url == origin()) {
            ASSERT_TRUE(type() == kFileSystemTypeTemporary);
            EXPECT_TRUE(HasFileSystemType(enumerator.get(),
                kFileSystemTypeTemporary));
            EXPECT_FALSE(HasFileSystemType(enumerator.get(),
                kFileSystemTypePersistent));
            found = true;
        }
        EXPECT_TRUE(found);
    }

    std::set<GURL> diff;
    std::set_symmetric_difference(origins_expected.begin(),
        origins_expected.end(), origins_found.begin(), origins_found.end(),
        inserter(diff, diff.begin()));
    EXPECT_TRUE(diff.empty());
}

TEST_F(ObfuscatedFileUtilTest, TestRevokeUsageCache)
{
    std::unique_ptr<FileSystemOperationContext> context(NewContext(NULL));

    int64_t expected_quota = 0;

    for (size_t i = 0; i < kRegularFileSystemTestCaseSize; ++i) {
        SCOPED_TRACE(testing::Message() << "Creating kRegularTestCase " << i);
        const FileSystemTestCaseRecord& test_case = kRegularFileSystemTestCases[i];
        base::FilePath file_path(test_case.path);
        expected_quota += ObfuscatedFileUtil::ComputeFilePathCost(file_path);
        if (test_case.is_directory) {
            bool exclusive = true;
            bool recursive = false;
            ASSERT_EQ(base::File::FILE_OK,
                ofu()->CreateDirectory(context.get(), CreateURL(file_path),
                    exclusive, recursive));
        } else {
            bool created = false;
            ASSERT_EQ(base::File::FILE_OK,
                ofu()->EnsureFileExists(context.get(), CreateURL(file_path),
                    &created));
            ASSERT_TRUE(created);
            ASSERT_EQ(base::File::FILE_OK,
                ofu()->Truncate(context.get(), CreateURL(file_path),
                    test_case.data_file_size));
            expected_quota += test_case.data_file_size;
        }
    }

    // Usually raw size in usage cache and the usage returned by QuotaUtil
    // should be same.
    EXPECT_EQ(expected_quota, SizeInUsageFile());
    EXPECT_EQ(expected_quota, SizeByQuotaUtil());

    RevokeUsageCache();
    EXPECT_EQ(-1, SizeInUsageFile());
    EXPECT_EQ(expected_quota, SizeByQuotaUtil());

    // This should reconstruct the cache.
    GetUsageFromQuotaManager();
    EXPECT_EQ(expected_quota, SizeInUsageFile());
    EXPECT_EQ(expected_quota, SizeByQuotaUtil());
    EXPECT_EQ(expected_quota, usage());
}

TEST_F(ObfuscatedFileUtilTest, TestInconsistency)
{
    const FileSystemURL kPath1 = CreateURLFromUTF8("hoge");
    const FileSystemURL kPath2 = CreateURLFromUTF8("fuga");

    std::unique_ptr<FileSystemOperationContext> context;
    base::File::Info file_info;
    base::FilePath data_path;
    bool created = false;

    // Create a non-empty file.
    context.reset(NewContext(NULL));
    EXPECT_EQ(base::File::FILE_OK,
        ofu()->EnsureFileExists(context.get(), kPath1, &created));
    EXPECT_TRUE(created);
    context.reset(NewContext(NULL));
    EXPECT_EQ(base::File::FILE_OK,
        ofu()->Truncate(context.get(), kPath1, 10));
    context.reset(NewContext(NULL));
    EXPECT_EQ(base::File::FILE_OK,
        ofu()->GetFileInfo(
            context.get(), kPath1, &file_info, &data_path));
    EXPECT_EQ(10, file_info.size);

    // Destroy database to make inconsistency between database and filesystem.
    ofu()->DestroyDirectoryDatabase(origin(), type_string());

    // Try to get file info of broken file.
    EXPECT_FALSE(PathExists(kPath1));
    context.reset(NewContext(NULL));
    EXPECT_EQ(base::File::FILE_OK,
        ofu()->EnsureFileExists(context.get(), kPath1, &created));
    EXPECT_TRUE(created);
    context.reset(NewContext(NULL));
    EXPECT_EQ(base::File::FILE_OK,
        ofu()->GetFileInfo(
            context.get(), kPath1, &file_info, &data_path));
    EXPECT_EQ(0, file_info.size);

    // Make another broken file to |kPath2|.
    context.reset(NewContext(NULL));
    EXPECT_EQ(base::File::FILE_OK,
        ofu()->EnsureFileExists(context.get(), kPath2, &created));
    EXPECT_TRUE(created);

    // Destroy again.
    ofu()->DestroyDirectoryDatabase(origin(), type_string());

    // Repair broken |kPath1|.
    context.reset(NewContext(NULL));
    EXPECT_EQ(base::File::FILE_ERROR_NOT_FOUND,
        ofu()->Touch(context.get(), kPath1, base::Time::Now(),
            base::Time::Now()));
    EXPECT_EQ(base::File::FILE_OK,
        ofu()->EnsureFileExists(context.get(), kPath1, &created));
    EXPECT_TRUE(created);

    // Copy from sound |kPath1| to broken |kPath2|.
    context.reset(NewContext(NULL));
    EXPECT_EQ(base::File::FILE_OK,
        ofu()->CopyOrMoveFile(context.get(), kPath1, kPath2,
            FileSystemOperation::OPTION_NONE,
            true /* copy */));

    ofu()->DestroyDirectoryDatabase(origin(), type_string());
    context.reset(NewContext(NULL));
    base::File file = ofu()->CreateOrOpen(context.get(), kPath1,
        base::File::FLAG_READ | base::File::FLAG_CREATE);
    EXPECT_TRUE(file.IsValid());
    EXPECT_TRUE(file.created());

    EXPECT_TRUE(file.GetInfo(&file_info));
    EXPECT_EQ(0, file_info.size);
}

TEST_F(ObfuscatedFileUtilTest, TestIncompleteDirectoryReading)
{
    const FileSystemURL kPath[] = {
        CreateURLFromUTF8("foo"),
        CreateURLFromUTF8("bar"),
        CreateURLFromUTF8("baz")
    };
    const FileSystemURL empty_path = CreateURL(base::FilePath());
    std::unique_ptr<FileSystemOperationContext> context;

    for (size_t i = 0; i < arraysize(kPath); ++i) {
        bool created = false;
        context.reset(NewContext(NULL));
        EXPECT_EQ(base::File::FILE_OK,
            ofu()->EnsureFileExists(context.get(), kPath[i], &created));
        EXPECT_TRUE(created);
    }

    std::vector<storage::DirectoryEntry> entries;
    EXPECT_EQ(base::File::FILE_OK,
        AsyncFileTestHelper::ReadDirectory(
            file_system_context(), empty_path, &entries));
    EXPECT_EQ(3u, entries.size());

    base::FilePath local_path;
    EXPECT_EQ(base::File::FILE_OK,
        ofu()->GetLocalFilePath(context.get(), kPath[0], &local_path));
    EXPECT_TRUE(base::DeleteFile(local_path, false));

    entries.clear();
    EXPECT_EQ(base::File::FILE_OK,
        AsyncFileTestHelper::ReadDirectory(
            file_system_context(), empty_path, &entries));
    EXPECT_EQ(arraysize(kPath) - 1, entries.size());
}

TEST_F(ObfuscatedFileUtilTest, TestDirectoryTimestampForCreation)
{
    std::unique_ptr<FileSystemOperationContext> context(NewContext(NULL));
    const FileSystemURL dir_url = CreateURLFromUTF8("foo_dir");

    // Create working directory.
    EXPECT_EQ(base::File::FILE_OK,
        ofu()->CreateDirectory(context.get(), dir_url, false, false));

    // EnsureFileExists, create case.
    FileSystemURL url(FileSystemURLAppendUTF8(dir_url, "EnsureFileExists_file"));
    bool created = false;
    ClearTimestamp(dir_url);
    context.reset(NewContext(NULL));
    EXPECT_EQ(base::File::FILE_OK,
        ofu()->EnsureFileExists(context.get(), url, &created));
    EXPECT_TRUE(created);
    EXPECT_NE(base::Time(), GetModifiedTime(dir_url));

    // non create case.
    created = true;
    ClearTimestamp(dir_url);
    context.reset(NewContext(NULL));
    EXPECT_EQ(base::File::FILE_OK,
        ofu()->EnsureFileExists(context.get(), url, &created));
    EXPECT_FALSE(created);
    EXPECT_EQ(base::Time(), GetModifiedTime(dir_url));

    // fail case.
    url = FileSystemURLAppendUTF8(dir_url, "EnsureFileExists_dir");
    context.reset(NewContext(NULL));
    EXPECT_EQ(base::File::FILE_OK,
        ofu()->CreateDirectory(context.get(), url, false, false));

    ClearTimestamp(dir_url);
    context.reset(NewContext(NULL));
    EXPECT_EQ(base::File::FILE_ERROR_NOT_A_FILE,
        ofu()->EnsureFileExists(context.get(), url, &created));
    EXPECT_EQ(base::Time(), GetModifiedTime(dir_url));

    // CreateOrOpen, create case.
    url = FileSystemURLAppendUTF8(dir_url, "CreateOrOpen_file");
    ClearTimestamp(dir_url);
    context.reset(NewContext(NULL));
    base::File file = ofu()->CreateOrOpen(context.get(), url,
        base::File::FLAG_CREATE | base::File::FLAG_WRITE);

    EXPECT_TRUE(file.IsValid());
    EXPECT_TRUE(file.created());
    file.Close();
    EXPECT_NE(base::Time(), GetModifiedTime(dir_url));

    // open case.
    ClearTimestamp(dir_url);
    context.reset(NewContext(NULL));
    file = ofu()->CreateOrOpen(context.get(), url,
        base::File::FLAG_OPEN | base::File::FLAG_WRITE);
    EXPECT_TRUE(file.IsValid());
    EXPECT_FALSE(file.created());
    file.Close();
    EXPECT_EQ(base::Time(), GetModifiedTime(dir_url));

    // fail case
    ClearTimestamp(dir_url);
    context.reset(NewContext(NULL));
    file = ofu()->CreateOrOpen(context.get(), url,
        base::File::FLAG_CREATE | base::File::FLAG_WRITE);
    EXPECT_FALSE(file.IsValid());
    EXPECT_EQ(base::File::FILE_ERROR_EXISTS, file.error_details());
    EXPECT_EQ(base::Time(), GetModifiedTime(dir_url));

    // CreateDirectory, create case.
    // Creating CreateDirectory_dir and CreateDirectory_dir/subdir.
    url = FileSystemURLAppendUTF8(dir_url, "CreateDirectory_dir");
    FileSystemURL subdir_url(FileSystemURLAppendUTF8(url, "subdir"));
    ClearTimestamp(dir_url);
    context.reset(NewContext(NULL));
    EXPECT_EQ(base::File::FILE_OK,
        ofu()->CreateDirectory(context.get(), subdir_url,
            true /* exclusive */, true /* recursive */));
    EXPECT_NE(base::Time(), GetModifiedTime(dir_url));

    // create subdir case.
    // Creating CreateDirectory_dir/subdir2.
    subdir_url = FileSystemURLAppendUTF8(url, "subdir2");
    ClearTimestamp(dir_url);
    ClearTimestamp(url);
    context.reset(NewContext(NULL));
    EXPECT_EQ(base::File::FILE_OK,
        ofu()->CreateDirectory(context.get(), subdir_url,
            true /* exclusive */, true /* recursive */));
    EXPECT_EQ(base::Time(), GetModifiedTime(dir_url));
    EXPECT_NE(base::Time(), GetModifiedTime(url));

    // fail case.
    url = FileSystemURLAppendUTF8(dir_url, "CreateDirectory_dir");
    ClearTimestamp(dir_url);
    context.reset(NewContext(NULL));
    EXPECT_EQ(base::File::FILE_ERROR_EXISTS,
        ofu()->CreateDirectory(context.get(), url,
            true /* exclusive */, true /* recursive */));
    EXPECT_EQ(base::Time(), GetModifiedTime(dir_url));

    // CopyInForeignFile, create case.
    url = FileSystemURLAppendUTF8(dir_url, "CopyInForeignFile_file");
    FileSystemURL src_path = FileSystemURLAppendUTF8(
        dir_url, "CopyInForeignFile_src_file");
    context.reset(NewContext(NULL));
    EXPECT_EQ(base::File::FILE_OK,
        ofu()->EnsureFileExists(context.get(), src_path, &created));
    EXPECT_TRUE(created);
    base::FilePath src_local_path;
    context.reset(NewContext(NULL));
    EXPECT_EQ(base::File::FILE_OK,
        ofu()->GetLocalFilePath(context.get(), src_path, &src_local_path));

    ClearTimestamp(dir_url);
    context.reset(NewContext(NULL));
    EXPECT_EQ(base::File::FILE_OK,
        ofu()->CopyInForeignFile(context.get(),
            src_local_path,
            url));
    EXPECT_NE(base::Time(), GetModifiedTime(dir_url));
}

TEST_F(ObfuscatedFileUtilTest, TestDirectoryTimestampForDeletion)
{
    std::unique_ptr<FileSystemOperationContext> context(NewContext(NULL));
    const FileSystemURL dir_url = CreateURLFromUTF8("foo_dir");

    // Create working directory.
    EXPECT_EQ(base::File::FILE_OK,
        ofu()->CreateDirectory(context.get(), dir_url, false, false));

    // DeleteFile, delete case.
    FileSystemURL url = FileSystemURLAppendUTF8(
        dir_url, "DeleteFile_file");
    bool created = false;
    context.reset(NewContext(NULL));
    EXPECT_EQ(base::File::FILE_OK,
        ofu()->EnsureFileExists(context.get(), url, &created));
    EXPECT_TRUE(created);

    ClearTimestamp(dir_url);
    context.reset(NewContext(NULL));
    EXPECT_EQ(base::File::FILE_OK,
        ofu()->DeleteFile(context.get(), url));
    EXPECT_NE(base::Time(), GetModifiedTime(dir_url));

    // fail case.
    ClearTimestamp(dir_url);
    context.reset(NewContext(NULL));
    EXPECT_EQ(base::File::FILE_ERROR_NOT_FOUND,
        ofu()->DeleteFile(context.get(), url));
    EXPECT_EQ(base::Time(), GetModifiedTime(dir_url));

    // DeleteDirectory, fail case.
    url = FileSystemURLAppendUTF8(dir_url, "DeleteDirectory_dir");
    FileSystemURL file_path(FileSystemURLAppendUTF8(url, "pakeratta"));
    context.reset(NewContext(NULL));
    EXPECT_EQ(base::File::FILE_OK,
        ofu()->CreateDirectory(context.get(), url, true, true));
    created = false;
    context.reset(NewContext(NULL));
    EXPECT_EQ(base::File::FILE_OK,
        ofu()->EnsureFileExists(context.get(), file_path, &created));
    EXPECT_TRUE(created);

    ClearTimestamp(dir_url);
    context.reset(NewContext(NULL));
    EXPECT_EQ(base::File::FILE_ERROR_NOT_EMPTY,
        ofu()->DeleteDirectory(context.get(), url));
    EXPECT_EQ(base::Time(), GetModifiedTime(dir_url));

    // delete case.
    context.reset(NewContext(NULL));
    EXPECT_EQ(base::File::FILE_OK,
        ofu()->DeleteFile(context.get(), file_path));

    ClearTimestamp(dir_url);
    context.reset(NewContext(NULL));
    EXPECT_EQ(base::File::FILE_OK, ofu()->DeleteDirectory(context.get(), url));
    EXPECT_NE(base::Time(), GetModifiedTime(dir_url));
}

TEST_F(ObfuscatedFileUtilTest, TestDirectoryTimestampForCopyAndMove)
{
    TestDirectoryTimestampHelper(
        CreateURLFromUTF8("copy overwrite"), true, true);
    TestDirectoryTimestampHelper(
        CreateURLFromUTF8("copy non-overwrite"), true, false);
    TestDirectoryTimestampHelper(
        CreateURLFromUTF8("move overwrite"), false, true);
    TestDirectoryTimestampHelper(
        CreateURLFromUTF8("move non-overwrite"), false, false);
}

TEST_F(ObfuscatedFileUtilTest, TestFileEnumeratorTimestamp)
{
    FileSystemURL dir = CreateURLFromUTF8("foo");
    FileSystemURL url1 = FileSystemURLAppendUTF8(dir, "bar");
    FileSystemURL url2 = FileSystemURLAppendUTF8(dir, "baz");

    std::unique_ptr<FileSystemOperationContext> context(NewContext(NULL));
    EXPECT_EQ(base::File::FILE_OK,
        ofu()->CreateDirectory(context.get(), dir, false, false));

    bool created = false;
    context.reset(NewContext(NULL));
    EXPECT_EQ(base::File::FILE_OK,
        ofu()->EnsureFileExists(context.get(), url1, &created));
    EXPECT_TRUE(created);

    context.reset(NewContext(NULL));
    EXPECT_EQ(base::File::FILE_OK,
        ofu()->CreateDirectory(context.get(), url2, false, false));

    base::FilePath file_path;
    context.reset(NewContext(NULL));
    EXPECT_EQ(base::File::FILE_OK,
        ofu()->GetLocalFilePath(context.get(), url1, &file_path));
    EXPECT_FALSE(file_path.empty());

    context.reset(NewContext(NULL));
    EXPECT_EQ(base::File::FILE_OK,
        ofu()->Touch(context.get(), url1,
            base::Time::Now() + base::TimeDelta::FromHours(1),
            base::Time()));

    context.reset(NewContext(NULL));
    std::unique_ptr<storage::FileSystemFileUtil::AbstractFileEnumerator>
        file_enum(ofu()->CreateFileEnumerator(context.get(), dir, false));

    int count = 0;
    base::FilePath file_path_each;
    while (!(file_path_each = file_enum->Next()).empty()) {
        context.reset(NewContext(NULL));
        base::File::Info file_info;
        base::FilePath file_path;
        EXPECT_EQ(base::File::FILE_OK,
            ofu()->GetFileInfo(context.get(),
                FileSystemURL::CreateForTest(
                    dir.origin(),
                    dir.mount_type(),
                    file_path_each),
                &file_info, &file_path));
        EXPECT_EQ(file_info.is_directory, file_enum->IsDirectory());
        EXPECT_EQ(file_info.last_modified, file_enum->LastModifiedTime());
        EXPECT_EQ(file_info.size, file_enum->Size());
        ++count;
    }
    EXPECT_EQ(2, count);
}

// crbug.com/176470
#if defined(OS_WIN) || defined(OS_ANDROID)
#define MAYBE_TestQuotaOnCopyFile DISABLED_TestQuotaOnCopyFile
#else
#define MAYBE_TestQuotaOnCopyFile TestQuotaOnCopyFile
#endif
TEST_F(ObfuscatedFileUtilTest, MAYBE_TestQuotaOnCopyFile)
{
    FileSystemURL from_file(CreateURLFromUTF8("fromfile"));
    FileSystemURL obstacle_file(CreateURLFromUTF8("obstaclefile"));
    FileSystemURL to_file1(CreateURLFromUTF8("tofile1"));
    FileSystemURL to_file2(CreateURLFromUTF8("tofile2"));
    bool created;

    int64_t expected_total_file_size = 0;
    ASSERT_EQ(base::File::FILE_OK,
        ofu()->EnsureFileExists(
            AllowUsageIncrease(PathCost(from_file))->context(),
            from_file, &created));
    ASSERT_TRUE(created);
    ASSERT_EQ(expected_total_file_size, ComputeTotalFileSize());

    ASSERT_EQ(base::File::FILE_OK,
        ofu()->EnsureFileExists(
            AllowUsageIncrease(PathCost(obstacle_file))->context(),
            obstacle_file, &created));
    ASSERT_TRUE(created);
    ASSERT_EQ(expected_total_file_size, ComputeTotalFileSize());

    int64_t from_file_size = 1020;
    expected_total_file_size += from_file_size;
    ASSERT_EQ(base::File::FILE_OK,
        ofu()->Truncate(
            AllowUsageIncrease(from_file_size)->context(),
            from_file, from_file_size));
    ASSERT_EQ(expected_total_file_size, ComputeTotalFileSize());

    int64_t obstacle_file_size = 1;
    expected_total_file_size += obstacle_file_size;
    ASSERT_EQ(base::File::FILE_OK,
        ofu()->Truncate(
            AllowUsageIncrease(obstacle_file_size)->context(),
            obstacle_file, obstacle_file_size));
    ASSERT_EQ(expected_total_file_size, ComputeTotalFileSize());

    int64_t to_file1_size = from_file_size;
    expected_total_file_size += to_file1_size;
    ASSERT_EQ(base::File::FILE_OK,
        ofu()->CopyOrMoveFile(
            AllowUsageIncrease(
                PathCost(to_file1) + to_file1_size)
                ->context(),
            from_file, to_file1,
            FileSystemOperation::OPTION_NONE,
            true /* copy */));
    ASSERT_EQ(expected_total_file_size, ComputeTotalFileSize());

    ASSERT_EQ(base::File::FILE_ERROR_NO_SPACE,
        ofu()->CopyOrMoveFile(
            DisallowUsageIncrease(
                PathCost(to_file2) + from_file_size)
                ->context(),
            from_file, to_file2, FileSystemOperation::OPTION_NONE,
            true /* copy */));
    ASSERT_EQ(expected_total_file_size, ComputeTotalFileSize());

    int64_t old_obstacle_file_size = obstacle_file_size;
    obstacle_file_size = from_file_size;
    expected_total_file_size += obstacle_file_size - old_obstacle_file_size;
    ASSERT_EQ(base::File::FILE_OK,
        ofu()->CopyOrMoveFile(
            AllowUsageIncrease(
                obstacle_file_size - old_obstacle_file_size)
                ->context(),
            from_file, obstacle_file,
            FileSystemOperation::OPTION_NONE,
            true /* copy */));
    ASSERT_EQ(expected_total_file_size, ComputeTotalFileSize());

    int64_t old_from_file_size = from_file_size;
    from_file_size = old_from_file_size - 1;
    expected_total_file_size += from_file_size - old_from_file_size;
    ASSERT_EQ(base::File::FILE_OK,
        ofu()->Truncate(
            AllowUsageIncrease(
                from_file_size - old_from_file_size)
                ->context(),
            from_file, from_file_size));
    ASSERT_EQ(expected_total_file_size, ComputeTotalFileSize());

    // quota exceeded
    {
        old_obstacle_file_size = obstacle_file_size;
        obstacle_file_size = from_file_size;
        expected_total_file_size += obstacle_file_size - old_obstacle_file_size;
        std::unique_ptr<UsageVerifyHelper> helper = AllowUsageIncrease(obstacle_file_size - old_obstacle_file_size);
        helper->context()->set_allowed_bytes_growth(
            helper->context()->allowed_bytes_growth() - 1);
        ASSERT_EQ(base::File::FILE_OK,
            ofu()->CopyOrMoveFile(
                helper->context(),
                from_file, obstacle_file,
                FileSystemOperation::OPTION_NONE,
                true /* copy */));
        ASSERT_EQ(expected_total_file_size, ComputeTotalFileSize());
    }
}

TEST_F(ObfuscatedFileUtilTest, TestQuotaOnMoveFile)
{
    FileSystemURL from_file(CreateURLFromUTF8("fromfile"));
    FileSystemURL obstacle_file(CreateURLFromUTF8("obstaclefile"));
    FileSystemURL to_file(CreateURLFromUTF8("tofile"));
    bool created;

    int64_t expected_total_file_size = 0;
    ASSERT_EQ(base::File::FILE_OK,
        ofu()->EnsureFileExists(
            AllowUsageIncrease(PathCost(from_file))->context(),
            from_file, &created));
    ASSERT_TRUE(created);
    ASSERT_EQ(expected_total_file_size, ComputeTotalFileSize());

    int64_t from_file_size = 1020;
    expected_total_file_size += from_file_size;
    ASSERT_EQ(base::File::FILE_OK,
        ofu()->Truncate(
            AllowUsageIncrease(from_file_size)->context(),
            from_file, from_file_size));
    ASSERT_EQ(expected_total_file_size, ComputeTotalFileSize());

    from_file_size = 0;
    ASSERT_EQ(base::File::FILE_OK,
        ofu()->CopyOrMoveFile(
            AllowUsageIncrease(-PathCost(from_file) + PathCost(to_file))->context(),
            from_file, to_file,
            FileSystemOperation::OPTION_NONE,
            false /* move */));
    ASSERT_EQ(expected_total_file_size, ComputeTotalFileSize());

    ASSERT_EQ(base::File::FILE_OK,
        ofu()->EnsureFileExists(
            AllowUsageIncrease(PathCost(from_file))->context(),
            from_file, &created));
    ASSERT_TRUE(created);
    ASSERT_EQ(expected_total_file_size, ComputeTotalFileSize());

    ASSERT_EQ(base::File::FILE_OK,
        ofu()->EnsureFileExists(
            AllowUsageIncrease(PathCost(obstacle_file))->context(),
            obstacle_file, &created));
    ASSERT_TRUE(created);
    ASSERT_EQ(expected_total_file_size, ComputeTotalFileSize());

    from_file_size = 1020;
    expected_total_file_size += from_file_size;
    ASSERT_EQ(base::File::FILE_OK,
        ofu()->Truncate(
            AllowUsageIncrease(from_file_size)->context(),
            from_file, from_file_size));
    ASSERT_EQ(expected_total_file_size, ComputeTotalFileSize());

    int64_t obstacle_file_size = 1;
    expected_total_file_size += obstacle_file_size;
    ASSERT_EQ(base::File::FILE_OK,
        ofu()->Truncate(
            AllowUsageIncrease(1)->context(),
            obstacle_file, obstacle_file_size));
    ASSERT_EQ(expected_total_file_size, ComputeTotalFileSize());

    int64_t old_obstacle_file_size = obstacle_file_size;
    obstacle_file_size = from_file_size;
    from_file_size = 0;
    expected_total_file_size -= old_obstacle_file_size;
    ASSERT_EQ(base::File::FILE_OK,
        ofu()->CopyOrMoveFile(
            AllowUsageIncrease(
                -old_obstacle_file_size - PathCost(from_file))
                ->context(),
            from_file, obstacle_file,
            FileSystemOperation::OPTION_NONE,
            false /* move */));
    ASSERT_EQ(expected_total_file_size, ComputeTotalFileSize());

    ASSERT_EQ(base::File::FILE_OK,
        ofu()->EnsureFileExists(
            AllowUsageIncrease(PathCost(from_file))->context(),
            from_file, &created));
    ASSERT_TRUE(created);
    ASSERT_EQ(expected_total_file_size, ComputeTotalFileSize());

    from_file_size = 10;
    expected_total_file_size += from_file_size;
    ASSERT_EQ(base::File::FILE_OK,
        ofu()->Truncate(
            AllowUsageIncrease(from_file_size)->context(),
            from_file, from_file_size));
    ASSERT_EQ(expected_total_file_size, ComputeTotalFileSize());

    // quota exceeded even after operation
    old_obstacle_file_size = obstacle_file_size;
    obstacle_file_size = from_file_size;
    from_file_size = 0;
    expected_total_file_size -= old_obstacle_file_size;
    std::unique_ptr<FileSystemOperationContext> context = LimitedContext(-old_obstacle_file_size - PathCost(from_file) - 1);
    ASSERT_EQ(base::File::FILE_OK,
        ofu()->CopyOrMoveFile(
            context.get(), from_file, obstacle_file,
            FileSystemOperation::OPTION_NONE,
            false /* move */));
    ASSERT_EQ(expected_total_file_size, ComputeTotalFileSize());
    context.reset();
}

TEST_F(ObfuscatedFileUtilTest, TestQuotaOnRemove)
{
    FileSystemURL dir(CreateURLFromUTF8("dir"));
    FileSystemURL file(CreateURLFromUTF8("file"));
    FileSystemURL dfile1(CreateURLFromUTF8("dir/dfile1"));
    FileSystemURL dfile2(CreateURLFromUTF8("dir/dfile2"));
    bool created;

    ASSERT_EQ(base::File::FILE_OK,
        ofu()->EnsureFileExists(
            AllowUsageIncrease(PathCost(file))->context(),
            file, &created));
    ASSERT_TRUE(created);
    ASSERT_EQ(0, ComputeTotalFileSize());

    ASSERT_EQ(base::File::FILE_OK,
        ofu()->CreateDirectory(
            AllowUsageIncrease(PathCost(dir))->context(),
            dir, false, false));
    ASSERT_EQ(0, ComputeTotalFileSize());

    ASSERT_EQ(base::File::FILE_OK,
        ofu()->EnsureFileExists(
            AllowUsageIncrease(PathCost(dfile1))->context(),
            dfile1, &created));
    ASSERT_TRUE(created);
    ASSERT_EQ(0, ComputeTotalFileSize());

    ASSERT_EQ(base::File::FILE_OK,
        ofu()->EnsureFileExists(
            AllowUsageIncrease(PathCost(dfile2))->context(),
            dfile2, &created));
    ASSERT_TRUE(created);
    ASSERT_EQ(0, ComputeTotalFileSize());

    ASSERT_EQ(base::File::FILE_OK,
        ofu()->Truncate(
            AllowUsageIncrease(340)->context(),
            file, 340));
    ASSERT_EQ(340, ComputeTotalFileSize());

    ASSERT_EQ(base::File::FILE_OK,
        ofu()->Truncate(
            AllowUsageIncrease(1020)->context(),
            dfile1, 1020));
    ASSERT_EQ(1360, ComputeTotalFileSize());

    ASSERT_EQ(base::File::FILE_OK,
        ofu()->Truncate(
            AllowUsageIncrease(120)->context(),
            dfile2, 120));
    ASSERT_EQ(1480, ComputeTotalFileSize());

    ASSERT_EQ(base::File::FILE_OK,
        ofu()->DeleteFile(
            AllowUsageIncrease(-PathCost(file) - 340)->context(),
            file));
    ASSERT_EQ(1140, ComputeTotalFileSize());

    ASSERT_EQ(base::File::FILE_OK,
        AsyncFileTestHelper::Remove(
            file_system_context(), dir, true /* recursive */));
    ASSERT_EQ(0, ComputeTotalFileSize());
}

TEST_F(ObfuscatedFileUtilTest, TestQuotaOnOpen)
{
    FileSystemURL url(CreateURLFromUTF8("file"));

    bool created;
    // Creating a file.
    ASSERT_EQ(base::File::FILE_OK,
        ofu()->EnsureFileExists(
            AllowUsageIncrease(PathCost(url))->context(),
            url, &created));
    ASSERT_TRUE(created);
    ASSERT_EQ(0, ComputeTotalFileSize());

    // Opening it, which shouldn't change the usage.
    base::File file = ofu()->CreateOrOpen(AllowUsageIncrease(0)->context(), url,
        base::File::FLAG_OPEN | base::File::FLAG_WRITE);
    ASSERT_TRUE(file.IsValid());
    ASSERT_EQ(0, ComputeTotalFileSize());
    file.Close();

    const int length = 33;
    ASSERT_EQ(base::File::FILE_OK,
        ofu()->Truncate(
            AllowUsageIncrease(length)->context(), url, length));
    ASSERT_EQ(length, ComputeTotalFileSize());

    // Opening it with CREATE_ALWAYS flag, which should truncate the file size.
    file = ofu()->CreateOrOpen(
        AllowUsageIncrease(-length)->context(), url,
        base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE);
    ASSERT_TRUE(file.IsValid());
    ASSERT_EQ(0, ComputeTotalFileSize());
    file.Close();

    // Extending the file again.
    ASSERT_EQ(base::File::FILE_OK,
        ofu()->Truncate(
            AllowUsageIncrease(length)->context(), url, length));
    ASSERT_EQ(length, ComputeTotalFileSize());

    // Opening it with TRUNCATED flag, which should truncate the file size.
    file = ofu()->CreateOrOpen(
        AllowUsageIncrease(-length)->context(), url,
        base::File::FLAG_OPEN_TRUNCATED | base::File::FLAG_WRITE);
    ASSERT_TRUE(file.IsValid());
    ASSERT_EQ(0, ComputeTotalFileSize());
    file.Close();
}

TEST_F(ObfuscatedFileUtilTest, MaybeDropDatabasesAliveCase)
{
    MaybeDropDatabasesAliveCaseTestBody();
}

TEST_F(ObfuscatedFileUtilTest, MaybeDropDatabasesAlreadyDeletedCase)
{
    MaybeDropDatabasesAlreadyDeletedCaseTestBody();
}

TEST_F(ObfuscatedFileUtilTest, DestroyDirectoryDatabase_Isolated)
{
    DestroyDirectoryDatabase_IsolatedTestBody();
}

TEST_F(ObfuscatedFileUtilTest, GetDirectoryDatabase_Isolated)
{
    GetDirectoryDatabase_IsolatedTestBody();
}

TEST_F(ObfuscatedFileUtilTest, MigrationBackFromIsolated)
{
    MigrationBackFromIsolatedTestBody();
}

TEST_F(ObfuscatedFileUtilTest, OpenPathInNonDirectory)
{
    FileSystemURL url(CreateURLFromUTF8("file"));
    FileSystemURL path_in_file(CreateURLFromUTF8("file/file"));
    bool created;

    ASSERT_EQ(base::File::FILE_OK,
        ofu()->EnsureFileExists(UnlimitedContext().get(), url, &created));
    ASSERT_TRUE(created);

    int file_flags = base::File::FLAG_CREATE | base::File::FLAG_WRITE;
    base::File file = ofu()->CreateOrOpen(UnlimitedContext().get(), path_in_file, file_flags);
    ASSERT_FALSE(file.IsValid());
    ASSERT_EQ(base::File::FILE_ERROR_NOT_A_DIRECTORY, file.error_details());

    ASSERT_EQ(base::File::FILE_ERROR_NOT_A_DIRECTORY,
        ofu()->CreateDirectory(UnlimitedContext().get(),
            path_in_file,
            false /* exclusive */,
            false /* recursive */));
}

TEST_F(ObfuscatedFileUtilTest, CreateDirectory_NotADirectoryInRecursive)
{
    FileSystemURL file(CreateURLFromUTF8("file"));
    FileSystemURL path_in_file(CreateURLFromUTF8("file/child"));
    FileSystemURL path_in_file_in_file(
        CreateURLFromUTF8("file/child/grandchild"));
    bool created;

    ASSERT_EQ(base::File::FILE_OK,
        ofu()->EnsureFileExists(UnlimitedContext().get(), file, &created));
    ASSERT_TRUE(created);

    ASSERT_EQ(base::File::FILE_ERROR_NOT_A_DIRECTORY,
        ofu()->CreateDirectory(UnlimitedContext().get(),
            path_in_file,
            false /* exclusive */,
            true /* recursive */));
    ASSERT_EQ(base::File::FILE_ERROR_NOT_A_DIRECTORY,
        ofu()->CreateDirectory(UnlimitedContext().get(),
            path_in_file_in_file,
            false /* exclusive */,
            true /* recursive */));
}

TEST_F(ObfuscatedFileUtilTest, DeleteDirectoryForOriginAndType)
{
    const GURL origin1("http://www.example.com:12");
    const GURL origin2("http://www.example.com:1234");

    // Create origin directories.
    std::unique_ptr<SandboxFileSystemTestHelper> fs1(
        NewFileSystem(origin1, kFileSystemTypeTemporary));
    std::unique_ptr<SandboxFileSystemTestHelper> fs2(
        NewFileSystem(origin1, kFileSystemTypePersistent));
    std::unique_ptr<SandboxFileSystemTestHelper> fs3(
        NewFileSystem(origin2, kFileSystemTypeTemporary));
    std::unique_ptr<SandboxFileSystemTestHelper> fs4(
        NewFileSystem(origin2, kFileSystemTypePersistent));

    // Make sure directories for origin1 exist.
    base::File::Error error = base::File::FILE_ERROR_FAILED;
    ofu()->GetDirectoryForOriginAndType(
        origin1, GetTypeString(kFileSystemTypeTemporary), false, &error);
    ASSERT_EQ(base::File::FILE_OK, error);
    error = base::File::FILE_ERROR_FAILED;
    ofu()->GetDirectoryForOriginAndType(
        origin1, GetTypeString(kFileSystemTypePersistent), false, &error);
    ASSERT_EQ(base::File::FILE_OK, error);

    // Make sure directories for origin2 exist.
    error = base::File::FILE_ERROR_FAILED;
    ofu()->GetDirectoryForOriginAndType(
        origin2, GetTypeString(kFileSystemTypeTemporary), false, &error);
    ASSERT_EQ(base::File::FILE_OK, error);
    error = base::File::FILE_ERROR_FAILED;
    ofu()->GetDirectoryForOriginAndType(
        origin2, GetTypeString(kFileSystemTypePersistent), false, &error);
    ASSERT_EQ(base::File::FILE_OK, error);

    // Delete a directory for origin1's persistent filesystem.
    ofu()->DeleteDirectoryForOriginAndType(
        origin1, GetTypeString(kFileSystemTypePersistent));

    // The directory for origin1's temporary filesystem should not be removed.
    error = base::File::FILE_ERROR_FAILED;
    ofu()->GetDirectoryForOriginAndType(
        origin1, GetTypeString(kFileSystemTypeTemporary), false, &error);
    ASSERT_EQ(base::File::FILE_OK, error);

    // The directory for origin1's persistent filesystem should be removed.
    error = base::File::FILE_ERROR_FAILED;
    ofu()->GetDirectoryForOriginAndType(
        origin1, GetTypeString(kFileSystemTypePersistent), false, &error);
    ASSERT_EQ(base::File::FILE_ERROR_NOT_FOUND, error);

    // The directories for origin2 should not be removed.
    error = base::File::FILE_ERROR_FAILED;
    ofu()->GetDirectoryForOriginAndType(
        origin2, GetTypeString(kFileSystemTypeTemporary), false, &error);
    ASSERT_EQ(base::File::FILE_OK, error);
    error = base::File::FILE_ERROR_FAILED;
    ofu()->GetDirectoryForOriginAndType(
        origin2, GetTypeString(kFileSystemTypePersistent), false, &error);
    ASSERT_EQ(base::File::FILE_OK, error);
}

TEST_F(ObfuscatedFileUtilTest, DeleteDirectoryForOriginAndType_DeleteAll)
{
    const GURL origin1("http://www.example.com:12");
    const GURL origin2("http://www.example.com:1234");

    // Create origin directories.
    std::unique_ptr<SandboxFileSystemTestHelper> fs1(
        NewFileSystem(origin1, kFileSystemTypeTemporary));
    std::unique_ptr<SandboxFileSystemTestHelper> fs2(
        NewFileSystem(origin1, kFileSystemTypePersistent));
    std::unique_ptr<SandboxFileSystemTestHelper> fs3(
        NewFileSystem(origin2, kFileSystemTypeTemporary));
    std::unique_ptr<SandboxFileSystemTestHelper> fs4(
        NewFileSystem(origin2, kFileSystemTypePersistent));

    // Make sure directories for origin1 exist.
    base::File::Error error = base::File::FILE_ERROR_FAILED;
    ofu()->GetDirectoryForOriginAndType(
        origin1, GetTypeString(kFileSystemTypeTemporary), false, &error);
    ASSERT_EQ(base::File::FILE_OK, error);
    error = base::File::FILE_ERROR_FAILED;
    ofu()->GetDirectoryForOriginAndType(
        origin1, GetTypeString(kFileSystemTypePersistent), false, &error);
    ASSERT_EQ(base::File::FILE_OK, error);

    // Make sure directories for origin2 exist.
    error = base::File::FILE_ERROR_FAILED;
    ofu()->GetDirectoryForOriginAndType(
        origin2, GetTypeString(kFileSystemTypeTemporary), false, &error);
    ASSERT_EQ(base::File::FILE_OK, error);
    error = base::File::FILE_ERROR_FAILED;
    ofu()->GetDirectoryForOriginAndType(
        origin2, GetTypeString(kFileSystemTypePersistent), false, &error);
    ASSERT_EQ(base::File::FILE_OK, error);

    // Delete all directories for origin1.
    ofu()->DeleteDirectoryForOriginAndType(origin1, std::string());

    // The directories for origin1 should be removed.
    error = base::File::FILE_ERROR_FAILED;
    ofu()->GetDirectoryForOriginAndType(
        origin1, GetTypeString(kFileSystemTypeTemporary), false, &error);
    ASSERT_EQ(base::File::FILE_ERROR_NOT_FOUND, error);
    error = base::File::FILE_ERROR_FAILED;
    ofu()->GetDirectoryForOriginAndType(
        origin1, GetTypeString(kFileSystemTypePersistent), false, &error);
    ASSERT_EQ(base::File::FILE_ERROR_NOT_FOUND, error);

    // The directories for origin2 should not be removed.
    error = base::File::FILE_ERROR_FAILED;
    ofu()->GetDirectoryForOriginAndType(
        origin2, GetTypeString(kFileSystemTypeTemporary), false, &error);
    ASSERT_EQ(base::File::FILE_OK, error);
    error = base::File::FILE_ERROR_FAILED;
    ofu()->GetDirectoryForOriginAndType(
        origin2, GetTypeString(kFileSystemTypePersistent), false, &error);
    ASSERT_EQ(base::File::FILE_OK, error);
}

} // namespace content
